diff --git a/README.md b/README.md
index 8bbc29c..dff49ad 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
# kippt-opera
-This is Kippt.com's official Firefox add-on. It's licensed under MIT and we'll accept improvements in pull-requests.
+This is Kippt.com's official Opera add-on. It's licensed under MIT and we'll accept improvements in pull-requests.
-Originally created by @armen138
+Originally created by @armen138.
## Building extension
-To build the extension, add the files to .zip and thne change the extension of the file to .oex
+* 1. Go to "opera://extensions", click on the "Developer mode".
+* 2. Click on "Load unpacked extension..." and then select the "kippt-opera".
+* 3. Restart Opera after succesful installing.
-NB. The extension will only play nice with pages that were loaded after the extension was activated.
-To be sure all works as it should, restart Opera after installing.
+NOTE: The extension will only play nice with pages that were loaded after the extension was activated.
diff --git a/includes/getinfo.js b/includes/getinfo.js
index cf2b213..72bb090 100644
--- a/includes/getinfo.js
+++ b/includes/getinfo.js
@@ -1,13 +1,15 @@
+opera.isReady(function(){
opera.extension.onmessage = function(event) {
- var kippt = {},
- titles = document.documentElement.getElementsByTagName("title");
- kippt.title = "";
- if(titles.length > 0) {
- kippt.title = titles[0].innerHTML;
- }
- kippt.url = window.location.toString();
- kippt.notes = window.getSelection().toString();
- if(event.data === "getinfo" && kippt.url.indexOf("widget") === -1) {
- event.source.postMessage(kippt);
- }
+ var kippt = {
+ }, titles = document.documentElement.getElementsByTagName("title");
+ kippt.title = "";
+ if (titles.length > 0) {
+ kippt.title = titles[0].innerHTML;
+ }
+ kippt.url = window.location.toString();
+ kippt.notes = window.getSelection().toString();
+ if (event.data === "getinfo" && kippt.url.indexOf("widget") === -1) {
+ event.source.postMessage(kippt);
+ }
};
+});
diff --git a/index.html b/index.html
index 9cc54d7..9f08913 100644
--- a/index.html
+++ b/index.html
@@ -1,41 +1 @@
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/inline_script__1.js b/inline_script__1.js
new file mode 100644
index 0000000..af0390d
--- /dev/null
+++ b/inline_script__1.js
@@ -0,0 +1,13 @@
+opera.isReady(function(){
+var url = window["url"] = "", title = window["title"] = "";
+window.addEventListener('DOMContentLoaded', function() {
+ opera.extension.postMessage('getme');
+}, false);
+opera.extension.onmessage = function(event) {
+ var iframe = document.getElementById("kippt");
+ url = event.data.url;
+ title = event.data.title;
+ notes = event.data.notes || "";
+ iframe.src = 'https://kippt.com/extensions/new/?url=' + encodeURIComponent(url) + '&title=' + encodeURIComponent(title) + '&source=opera¬es=' + encodeURIComponent(notes);
+};
+});
diff --git a/inline_script_index_1.js b/inline_script_index_1.js
new file mode 100644
index 0000000..23bdae0
--- /dev/null
+++ b/inline_script_index_1.js
@@ -0,0 +1,34 @@
+opera.isReady(function(){
+var ext = window["ext"] = window.opera.extension, kippt = window["kippt"] = {
+}, tabs = window["tabs"] = {
+}, popup = window["popup"] = null, button = window["button"];
+window.addEventListener("load", function() {
+ ToolbarUIItemProperties = {
+ title: "Kippt",
+ icon: "kippt.png",
+ popup: {
+ href: "pop.html",
+ width: 420,
+ height: 235
+ }
+ };
+ button = opera.contexts.toolbar.createItem(ToolbarUIItemProperties);
+ opera.contexts.toolbar.addItem(button);
+}, false);
+ext.onmessage = function(event) {
+ var tab = ext.tabs.getFocused(), url = tab ? tab.url : "";
+ if (event.data === "getme") {
+ popup = event.source;
+ if (tab) {
+ tab.postMessage("getinfo");
+ } else {
+ popup.postMessage({
+ url: "",
+ title: ""
+ });
+ }
+ } else {
+ popup.postMessage(event.data);
+ }
+};
+});
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..52b1ea9
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,15 @@
+{
+"name": "Kippt",
+"developer": {"name" : "Kippt", "url" : "http://kippt.com"},
+"description" : "Kippt's Opera add-on - http://kippt.com",
+"manifest_version" : 2,
+"version" : "0.1",
+"background" : {"page" : "index.html"},
+"icons" : {"128": "kippt.png"},
+"browser_action" : {},
+"content_scripts": [
+{"js": ["oex_shim/operaextensions_injectedscript.js", "includes/getinfo.js"], "matches": [""], "include_globs": ["*"], "exclude_globs": [], "run_at": "document_start", "all_frames" : true}],
+"web_accessible_resources" : ["includes/getinfo.js","kippt.png","kippt_big.png","kippt_icon.png","LICENSE","pop.html","README.md"],
+"permissions" : ["tabs", "https://*/*", "storage", "http://*/*"],
+"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'unsafe-eval';"
+}
diff --git a/oex_shim/operaextensions_background.js b/oex_shim/operaextensions_background.js
new file mode 100644
index 0000000..d4d130c
--- /dev/null
+++ b/oex_shim/operaextensions_background.js
@@ -0,0 +1,15095 @@
+!(function( global ) {
+
+ var Opera = function() {};
+
+ Opera.prototype.REVISION = '1';
+
+ Opera.prototype.version = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.buildNumber = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.postError = function( str ) {
+ console.log( str );
+ };
+
+ var opera = global.opera || new Opera();
+
+ var manifest = chrome.app.getDetails(); // null in injected scripts / popups
+
+ navigator.browserLanguage=navigator.language; //Opera defines both, some extensions use the former
+
+ var isReady = false;
+
+ var _delayedExecuteEvents = [
+ // Example:
+ // { 'target': opera.extension, 'methodName': 'message', 'args': event }
+ ];
+
+ // Pick the right base URL for new tab generation
+ var newTab_BaseURL = 'data:text/html,Loading... ';
+
+ function addDelayedEvent(target, methodName, args) {
+ if(isReady) {
+ target[methodName].apply(target, args);
+ } else {
+ _delayedExecuteEvents.push({
+ "target": target,
+ "methodName": methodName,
+ "args": args
+ });
+ }
+ };
+
+// Used to trigger opera.isReady() functions
+var deferredComponentsLoadStatus = {
+ 'WINTABS_LOADED': false,
+ 'WIDGET_API_LOADED': false,
+ 'WIDGET_PREFERENCES_LOADED': false,
+ 'SPEEDDIAL_LOADED': false
+ // ...etc
+};
+
+/**
+ * rsvp.js
+ *
+ * Author: Tilde, Inc.
+ * URL: https://github.com/tildeio/rsvp.js
+ * Licensed under MIT License
+ *
+ * Customized for use in operaextensions.js
+ * By: Rich Tibbett
+ */
+
+ var exports = {};
+ var browserGlobal = (typeof window !== 'undefined') ? window : {};
+
+ var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var async;
+
+ if (typeof process !== 'undefined' &&
+ {}.toString.call(process) === '[object process]') {
+ async = function(callback, binding) {
+ process.nextTick(function() {
+ callback.call(binding);
+ });
+ };
+ } else if (MutationObserver) {
+ var queue = [];
+
+ var observer = new MutationObserver(function() {
+ var toProcess = queue.slice();
+ queue = [];
+
+ toProcess.forEach(function(tuple) {
+ var callback = tuple[0], binding = tuple[1];
+ callback.call(binding);
+ });
+ });
+
+ var element = document.createElement('div');
+ observer.observe(element, { attributes: true });
+
+ async = function(callback, binding) {
+ queue.push([callback, binding]);
+ element.setAttribute('drainQueue', 'drainQueue');
+ };
+ } else {
+ async = function(callback, binding) {
+ setTimeout(function() {
+ callback.call(binding);
+ }, 1);
+ };
+ }
+
+ exports.async = async;
+
+ var Event = exports.Event = function(type, options) {
+ this.type = type;
+
+ for (var option in options) {
+ if (!options.hasOwnProperty(option)) { continue; }
+
+ this[option] = options[option];
+ }
+ };
+
+ var indexOf = function(callbacks, callback) {
+ for (var i=0, l=callbacks.length; i 1 ) {
+ t -= 1;
+ }
+ if ( t < 1 / 6 ) {
+ return p + ( q - p ) * 6 * t;
+ }
+ if ( t < 1 / 2 ) {
+ return q;
+ }
+ if ( t < 2 / 3 ) {
+ return p + ( q - p ) * ( 2 / 3 - t ) * 6;
+ }
+ return p;
+ };
+
+ var toRGB = {
+ rgb: function( bits ) {
+ return [ bits[1], bits[2], bits[3], bits[4] || 1 ];
+ },
+ hsl: function( bits ) {
+ var hsl = {
+ h : parseInt( bits[ 1 ], 10 ) % 360 / 360,
+ s : parseInt( bits[ 2 ], 10 ) % 101 / 100,
+ l : parseInt( bits[ 3 ], 10 ) % 101 / 100,
+ a : bits[4] || 1
+ };
+
+ if ( hsl.s === 0 ) {
+ return [ hsl.l, hsl.l, hsl.l ];
+ }
+
+ var q = hsl.l < 0.5 ? hsl.l * ( 1 + hsl.s ) : hsl.l + hsl.s - hsl.l * hsl.s;
+ var p = 2 * hsl.l - q;
+
+ return [
+ ( hueToRgb( p, q, hsl.h + 1 / 3 ) * 256 ).toFixed( 0 ),
+ ( hueToRgb( p, q, hsl.h ) * 256 ).toFixed( 0 ),
+ ( hueToRgb( p, q, hsl.h - 1 / 3 ) * 256 ).toFixed( 0 ),
+ hsl.a
+ ];
+ },
+ hsv: function( bits ) {
+ var rgb = {},
+ hsv = {
+ h : parseInt( bits[ 1 ], 10 ) % 360 / 360,
+ s : parseInt( bits[ 2 ], 10 ) % 101 / 100,
+ v : parseInt( bits[ 3 ], 10 ) % 101 / 100
+ },
+ i = Math.floor( hsv.h * 6 ),
+ f = hsv.h * 6 - i,
+ p = hsv.v * ( 1 - hsv.s ),
+ q = hsv.v * ( 1 - f * hsv.s ),
+ t = hsv.v * ( 1 - ( 1 - f ) * hsv.s );
+
+ switch( i % 6 ) {
+ case 0:
+ rgb.r = hsv.v; rgb.g = t; rgb.b = p;
+ break;
+ case 1:
+ rgb.r = q; rgb.g = hsv.v; rgb.b = p;
+ break;
+ case 2:
+ rgb.r = p; rgb.g = hsv.v; rgb.b = t;
+ break;
+ case 3:
+ rgb.r = p; rgb.g = q; rgb.b = hsv.v;
+ break;
+ case 4:
+ rgb.r = t; rgb.g = p; rgb.b = hsv.v;
+ break;
+ case 5:
+ rgb.r = hsv.v; rgb.g = p; rgb.b = q;
+ break;
+ }
+
+ return [ rgb.r * 256, rgb.g * 256, rgb.b * 256 ];
+ }
+ };
+
+ function DectoHex( dec ) {
+ var hex = parseInt( dec, 10 );
+ hex = hex.toString(16);
+ return hex == 0 ? "00" : hex;
+ }
+
+ function applySaturation( rgb ) {
+ var alpha = parseFloat(rgb[3] || 1);
+ if(alpha + "" === "NaN" || alpha < 0 || alpha >= 1) {
+ return rgb;
+ }
+ if(alpha == 0) {
+ return [ 255, 255, 255 ];
+ }
+ return [
+ alpha * parseInt(rgb[0], 10) + (1 - alpha) * (backgroundColorVal || 255),
+ alpha * parseInt(rgb[1], 10) + (1 - alpha) * (backgroundColorVal || 255),
+ alpha * parseInt(rgb[2], 10) + (1 - alpha) * (backgroundColorVal || 255)
+ ]; // assumes background is white (255)
+ }
+
+ for(var i = 0, l = otherColorTypes.length; i < l; i++) {
+ var bits = otherColorTypes[i][1].exec( color );
+ if(bits) {
+ var rgbVal = applySaturation( toRGB[ otherColorTypes[i][0] ]( bits ) );
+ return "#" + DectoHex(rgbVal[0] || 255) + DectoHex(rgbVal[1] || 255) + DectoHex(rgbVal[2] || 255);
+ }
+ }
+
+ return "#f00"; // default in case of error
+
+};
+
+function OError(name, msg, code) {
+ Error.call(this);
+ Error.captureStackTrace(this, arguments.callee);
+ this.name = name || "Error";
+ this.code = code || -1;
+ this.message = msg || "";
+};
+
+OError.prototype.__proto__ = Error.prototype;
+
+var OEvent = function(eventType, eventProperties) {
+
+ var props = eventProperties || {};
+
+ var newEvt = new CustomEvent(eventType, true, true);
+
+ for(var i in props) {
+ newEvt[i] = props[i];
+ }
+
+ return newEvt;
+
+};
+
+var OEventTarget = function() {
+
+ EventTarget.mixin( this );
+
+};
+
+OEventTarget.prototype.constructor = OEventTarget;
+
+OEventTarget.prototype.addEventListener = function(eventName, callback, useCapture) {
+ this.on(eventName, callback); // no useCapture
+};
+
+OEventTarget.prototype.removeEventListener = function(eventName, callback, useCapture) {
+ this.off(eventName, callback); // no useCapture
+}
+
+OEventTarget.prototype.dispatchEvent = function( eventObj ) {
+
+ var eventName = eventObj.type;
+
+ // Register an onX functions registered for this event, if any
+ if(typeof this[ 'on' + eventName.toLowerCase() ] === 'function') {
+ this.on( eventName, this[ 'on' + eventName.toLowerCase() ] );
+ }
+
+ this.trigger( eventName, eventObj );
+
+};
+
+var OPromise = function() {
+
+ Promise.call( this );
+
+};
+
+OPromise.prototype = Object.create( Promise.prototype );
+
+// Add OEventTarget helper functions to OPromise prototype
+for(var i in OEventTarget.prototype) {
+ OPromise.prototype[i] = OEventTarget.prototype[i];
+}
+
+/**
+ * Queue for running multi-object promise-rooted asynchronous
+ * functions serially
+ */
+var Queue = (function() {
+ var _q = [], _lock = false, _timeout = 1000;
+
+ function callNext() {
+ _lock = false;
+ dequeue(); // auto-execute next queue item
+ }
+
+ function dequeue() {
+ if (_lock) {
+ return;
+ }
+ _lock = true; // only allow one accessor at a time
+
+ var item = _q.shift(); // pop the next item from the queue
+
+ if (item === undefined) {
+ _lock = false;
+ return; // end dequeuing
+ }
+ if (item.obj.isResolved) {
+ // execute queue item immediately
+ item.fn.call(item.obj, callNext);
+ } else {
+ if(item.ignoreResolve) {
+ item.fn.call(item.obj, callNext);
+ } else {
+ // break deadlocks
+ var timer = global.setTimeout(function() {
+ console.warn('PromiseQueue deadlock broken with timeout.');
+ console.log(item.obj);
+ console.log(item.obj.isResolved);
+ item.obj.trigger('promise:resolved'); // manual trigger / resolve
+ }, _timeout);
+
+ // execute queue item when obj resolves
+ item.obj.on('promise:resolved', function() {
+ if(timer) global.clearTimeout(timer);
+
+ item.obj.isResolved = true; // set too late in rsvp.js
+
+ item.fn.call(item.obj, callNext);
+ });
+ }
+ }
+ };
+
+ return {
+ enqueue: function(obj, fn, ignoreResolve) {
+ _q.push({ "obj": obj, "fn": fn, "ignoreResolve": ignoreResolve });
+ dequeue(); // auto-execute next queue item
+ },
+ dequeue: function() {
+ dequeue();
+ }
+ }
+})();
+
+var OMessagePort = function( isBackground ) {
+
+ OEventTarget.call( this );
+
+ this._isBackground = isBackground || false;
+
+ this._localPort = null;
+
+ // Every process, except the background process needs to connect up ports
+ if( !this._isBackground ) {
+
+ this._localPort = chrome.extension.connect({ "name": ("" + Math.floor( Math.random() * 1e16)) });
+
+ this._localPort.onDisconnect.addListener(function() {
+
+ this.dispatchEvent( new OEvent( 'disconnect', { "source": this._localPort } ) );
+
+ this._localPort = null;
+
+ }.bind(this));
+
+ var onMessageHandler = function( _message, _sender, responseCallback ) {
+
+ var localPort = this._localPort;
+
+ if(_message && _message.action && _message.action.indexOf('___O_') === 0) {
+
+ // Fire controlmessage events *immediately*
+ this.dispatchEvent( new OEvent(
+ 'controlmessage',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ localPort.postMessage( data );
+ },
+ "tabId": _sender && _sender.tab ? _sender.tab.id : null
+ }
+ }
+ ) );
+
+ } else {
+
+ // Fire 'message' event once we have all the initial listeners setup on the page
+ // so we don't miss any .onconnect call from the extension page.
+ // Or immediately if the shim isReady
+ addDelayedEvent(this, 'dispatchEvent', [ new OEvent(
+ 'message',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ localPort.postMessage( data );
+ },
+ "tabId": _sender && _sender.tab ? _sender.tab.id : null
+ }
+ }
+ ) ]);
+
+ }
+
+ if(responseCallback)responseCallback({});
+
+ }.bind(this);
+
+ this._localPort.onMessage.addListener( onMessageHandler );
+ chrome.extension.onMessage.addListener( onMessageHandler );
+
+
+ // Fire 'connect' event once we have all the initial listeners setup on the page
+ // so we don't miss any .onconnect call from the extension page
+ addDelayedEvent(this, 'dispatchEvent', [ new OEvent('connect', { "source": this._localPort, "origin": "" }) ]);
+
+ }
+
+};
+
+OMessagePort.prototype = Object.create( OEventTarget.prototype );
+
+OMessagePort.prototype.postMessage = function( data ) {
+
+ if( !this._isBackground ) {
+ if(this._localPort) {
+
+ this._localPort.postMessage( data );
+
+ }
+ } else {
+
+ this.broadcastMessage( data );
+
+ }
+
+};
+
+var OBackgroundMessagePort = function() {
+
+ OMessagePort.call( this, true );
+
+ this._allPorts = {};
+
+ chrome.extension.onConnect.addListener(function( _remotePort ) {
+
+ var portIndex = _remotePort['name'] || Math.floor(Math.random() * 1e16);
+
+ // When this port disconnects, remove _port from this._allPorts
+ _remotePort.onDisconnect.addListener(function() {
+
+ delete this._allPorts[ portIndex ];
+
+ this.dispatchEvent( new OEvent('disconnect', { "source": _remotePort }) );
+
+ }.bind(this));
+
+ this._allPorts[ portIndex ] = _remotePort;
+
+ _remotePort.onMessage.addListener( function( _message, _sender, responseCallback ) {
+
+ if(_message && _message.action && _message.action.indexOf('___O_') === 0) {
+
+ // Fire controlmessage events *immediately*
+ this.dispatchEvent( new OEvent(
+ 'controlmessage',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ _remotePort.postMessage( data );
+ },
+ "tabId": _remotePort.sender && _remotePort.sender.tab ? _remotePort.sender.tab.id : null
+ }
+ }
+ ) );
+
+ } else {
+
+ // Fire 'message' event once we have all the initial listeners setup on the page
+ // so we don't miss any .onconnect call from the extension page.
+ // Or immediately if the shim isReady
+ addDelayedEvent(this, 'dispatchEvent', [ new OEvent(
+ 'message',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ _remotePort.postMessage( data );
+ },
+ "tabId": _remotePort.sender && _remotePort.sender.tab ? _remotePort.sender.tab.id : null
+ }
+ }
+ ) ]);
+
+ }
+
+ }.bind(this) );
+
+ this.dispatchEvent( new OEvent('connect', { "source": _remotePort, "origin": "" }) );
+
+ }.bind(this));
+
+};
+
+OBackgroundMessagePort.prototype = Object.create( OMessagePort.prototype );
+
+OBackgroundMessagePort.prototype.broadcastMessage = function( data ) {
+
+ for(var activePort in this._allPorts) {
+ this._allPorts[ activePort ].postMessage( data );
+ }
+
+};
+
+var OperaExtension = function() {
+
+ OBackgroundMessagePort.call( this );
+
+};
+
+OperaExtension.prototype = Object.create( OBackgroundMessagePort.prototype );
+
+// Generate API stubs
+
+var OEX = opera.extension = opera.extension || new OperaExtension();
+
+var OEC = opera.contexts = opera.contexts || {};
+
+OperaExtension.prototype.getFile = function(path) {
+ var response = null;
+
+ if(typeof path != "string")return response;
+
+ try{
+ var host = chrome.extension.getURL('');
+
+ if(path.indexOf('widget:')==0)path = path.replace('widget:','chrome-extension:');
+ if(path.indexOf('/')==0)path = path.substring(1);
+
+ path = (path.indexOf(host)==-1?host:'')+path;
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadend = function(){
+ if (xhr.readyState==xhr.DONE && xhr.status==200){
+ result = xhr.response;
+
+ result.name = path.substring(path.lastIndexOf('/')+1);
+
+ result.lastModifiedDate = null;
+ result.toString = function(){
+ return "[object File]";
+ };
+ response = result;
+ };
+ };
+
+ xhr.open('GET',path,false);
+ xhr.responseType = 'blob';
+
+ xhr.send(null);
+
+ } catch(e){
+ return response;
+ };
+
+ return response;
+};
+
+var OStorage = function () {
+
+ // All attributes and methods defined in this class must be non-enumerable,
+ // hence the structure of this class and use of Object.defineProperty.
+
+ Object.defineProperty(this, "_storage", { value : localStorage });
+
+ Object.defineProperty(this, "length", { value : 0, writable:true });
+
+ // Copy all key/value pairs from localStorage on startup
+ for(var i in localStorage) {
+ this[i] = localStorage[i];
+ this.length++;
+ }
+
+ Object.defineProperty(OStorage.prototype, "getItem", {
+ value: function( key ) {
+ return this._storage.getItem(key);
+ }.bind(this)
+ });
+
+ Object.defineProperty(OStorage.prototype, "key", {
+ value: function( i ) {
+ return this._storage.key(i);
+ }.bind(this)
+ });
+
+ Object.defineProperty(OStorage.prototype, "removeItem", {
+ value: function( key, proxiedChange ) {
+ this._storage.removeItem(key);
+
+ if( this.hasOwnProperty( key ) ) {
+ delete this[key];
+ this.length--;
+ }
+
+ if( !proxiedChange ) {
+ OEX.postMessage({
+ "action": "___O_widgetPreferences_removeItem_RESPONSE",
+ "data": {
+ "key": key
+ }
+ });
+ }
+ }.bind(this)
+ });
+
+ Object.defineProperty(OStorage.prototype, "setItem", {
+ value: function( key, value, proxiedChange ) {
+ var oldVal = this._storage.getItem(key);
+
+ this._storage.setItem(key, value);
+
+ if( !this[key] ) {
+ this.length++;
+ }
+ this[key] = value;
+
+ if( !proxiedChange ) {
+ OEX.postMessage({
+ "action": "___O_widgetPreferences_setItem_RESPONSE",
+ "data": {
+ "key": key,
+ "val": value
+ }
+ });
+ }
+
+ // Create and fire 'storage' event on window object
+ var storageEvt = new OEvent('storage', {
+ "key": key,
+ "oldValue": oldVal,
+ "newValue": this._storage.getItem(key),
+ "url": chrome.extension.getURL(""),
+ "storageArea": this._storage
+ });
+ global.dispatchEvent( storageEvt );
+
+ }.bind(this)
+ });
+
+ Object.defineProperty(OStorage.prototype, "clear", {
+ value: function( proxiedChange ) {
+ this._storage.clear();
+
+ for(var i in this) {
+ if( this.hasOwnProperty( i ) ) {
+ delete this[ i ];
+ }
+ }
+ this.length = 0;
+
+ if( !proxiedChange ) {
+ OEX.postMessage({
+ "action": "___O_widgetPreferences_clearItem_RESPONSE"
+ });
+ }
+ }.bind(this)
+ });
+
+};
+
+// Inherit the standard Storage prototype
+OStorage.prototype = Object.create( Storage.prototype );
+
+var OWidgetObj = function() {
+
+ OEventTarget.call(this);
+
+ this.properties = manifest || chrome.app.getDetails();
+
+ // Set WIDGET_API_LOADED feature to LOADED
+ deferredComponentsLoadStatus['WIDGET_API_LOADED'] = true;
+
+ // LocalStorage shim
+ this._preferences = new OStorage();
+
+ // Set WIDGET_PREFERENCES_LOADED feature to LOADED
+ deferredComponentsLoadStatus['WIDGET_PREFERENCES_LOADED'] = true;
+
+ // Setup widget object proxy listener
+ // for injected scripts and popups to connect to
+ OEX.addEventListener('controlmessage', function( msg ) {
+
+ if( !msg.data || !msg.data.action ) {
+ return;
+ }
+
+ switch( msg.data.action ) {
+
+ // Set up all storage properties
+ case '___O_widget_setup_REQUEST':
+
+ var dataObj = {};
+ for(var i in this.properties) {
+ dataObj[ i ] = this.properties[ i ];
+ }
+
+ msg.source.postMessage({
+ "action": "___O_widget_setup_RESPONSE",
+ "attrs": dataObj,
+ // Add a copy of the preferences object
+ "_prefs": this._preferences
+ });
+
+ break;
+
+ // Update a storage item
+ case '___O_widgetPreferences_setItem_REQUEST':
+
+ this._preferences.setItem( msg.data.data.key, msg.data.data.val, true );
+
+ break;
+
+ // Remove a storage item
+ case '___O_widgetPreferences_removeItem_REQUEST':
+
+ this._preferences.removeItem( msg.data.data.key, true );
+
+ break;
+
+ // Clear all storage items
+ case '___O_widgetPreferences_clear_REQUEST':
+
+ this._preferences.clear( true );
+
+ break;
+
+ default:
+ break;
+ }
+
+ }.bind(this), false);
+
+};
+
+OWidgetObj.prototype = Object.create( OEventTarget.prototype );
+
+OWidgetObj.prototype.__defineGetter__('name', function() {
+ return this.properties.name || "";
+});
+
+OWidgetObj.prototype.__defineGetter__('shortName', function() {
+ return this.properties.name ? this.properties.name.short || "" : "";
+});
+
+OWidgetObj.prototype.__defineGetter__('id', function() {
+ return this.properties.id || "";
+});
+
+OWidgetObj.prototype.__defineGetter__('description', function() {
+ return this.properties.description || "";
+});
+
+OWidgetObj.prototype.__defineGetter__('author', function() {
+ return this.properties.author ? this.properties.author.name || "" : "";
+});
+
+OWidgetObj.prototype.__defineGetter__('authorHref', function() {
+ return this.properties.author ? this.properties.author.href || "" : "";
+});
+
+OWidgetObj.prototype.__defineGetter__('authorEmail', function() {
+ return this.properties.author ? this.properties.author.email || "" : "";
+});
+
+OWidgetObj.prototype.__defineGetter__('version', function() {
+ return this.properties.version || "";
+});
+
+OWidgetObj.prototype.__defineGetter__('preferences', function() {
+ return this._preferences;
+});
+
+// Add Widget API directly to global window
+global.widget = global.widget || new OWidgetObj();
+
+var BrowserWindowManager = function() {
+
+ OPromise.call(this);
+
+ this.length = 0;
+
+ // Set up the real BrowserWindow (& BrowserTab) objects available at start up time
+ chrome.windows.getAll({
+ populate: true
+ }, function(_windows) {
+
+ var _allTabs = [];
+
+ for (var i = 0, l = _windows.length; i < l; i++) {
+ var newWindow = new BrowserWindow(_windows[i]);
+
+ // Set properties not available in BrowserWindow constructor
+ newWindow.properties.id = _windows[i].id;
+ newWindow.properties.incognito = _windows[i].incognito;
+
+ this[i] = newWindow;
+ this.length = i + 1;
+
+ // Replace tab properties belonging to this window with real properties
+ var _tabs = [];
+ for (var j = 0, k = _windows[i].tabs.length; j < k; j++) {
+ _tabs[j] = new BrowserTab(_windows[i].tabs[j], this[i], true);
+
+ // Set properties not available in BrowserTab constructor
+ _tabs[j].properties.id = _windows[i].tabs[j].id;
+ _tabs[j].properties.active = _windows[i].tabs[j].active;
+ _tabs[j].properties.pinned = _windows[i].tabs[j].pinned;
+ _tabs[j].properties.status = _windows[i].tabs[j].status;
+ _tabs[j].properties.title = _windows[i].tabs[j].title;
+ _tabs[j].properties.favIconUrl = _windows[i].tabs[j].favIconUrl;
+ _tabs[j].properties.url = _windows[i].tabs[j].url;
+ _tabs[j].properties.index = _windows[i].tabs[j].index;
+ _tabs[j].properties.incognito = _windows[i].tabs[j].incognito;
+ }
+ this[i].tabs.replaceTabs(_tabs);
+
+ _allTabs = _allTabs.concat(_tabs);
+
+ }
+
+ // Replace tabs in root tab manager object
+ OEX.tabs.replaceTabs(_allTabs);
+
+ // Resolve root window manager
+ this.resolve(true);
+ // Resolve root tabs manager
+ OEX.tabs.resolve(true);
+
+ // Resolve objects.
+ //
+ // Resolution of each object in order:
+ // 1. Window
+ // 2. Window's Tab Manager
+ // 3. Window's Tab Manager's Tabs
+ for (var i = 0, l = this.length; i < l; i++) {
+ this[i].resolve(true);
+
+ this[i].tabs.resolve(true);
+
+ for (var j = 0, k = this[i].tabs.length; j < k; j++) {
+
+ this[i].tabs[j].resolve(true);
+
+ }
+ }
+
+ // Set WinTabs feature to LOADED
+ deferredComponentsLoadStatus['WINTABS_LOADED'] = true;
+
+ }.bind(this));
+
+ this.addWindow = function(windowId, windowObj) {
+
+ windowObj.properties.id = windowId;
+
+ this[this.length] = windowObj;
+ this.length += 1;
+
+ // Resolve object
+ windowObj.resolve(true);
+ windowObj.tabs.resolve(true);
+
+ // Fire a new 'create' event on this manager object
+ this.dispatchEvent(new OEvent('create', {
+ browserWindow: windowObj
+ }));
+
+ };
+
+ // Monitor ongoing window events
+
+ chrome.windows.onFocusChanged.addListener(function(windowId) {
+
+ var _prevFocusedWindow = this.getLastFocused();
+
+ // If no new window is focused, abort here
+ if( windowId !== chrome.windows.WINDOW_ID_NONE ) {
+
+ // Find and fire focus event on newly focused window
+ for (var i = 0, l = this.length; i < l; i++) {
+
+ if (this[i].properties.id == windowId && _prevFocusedWindow !== this[i] ) {
+
+ this[i].properties.focused = true;
+
+ } else {
+
+ this[i].properties.focused = false;
+
+ }
+
+ }
+
+ }
+
+ // Find and fire blur event on currently focused window
+ for (var i = 0, l = this.length; i < l; i++) {
+
+ if (this[i].properties.id !== windowId && this[i] == _prevFocusedWindow) {
+
+ this[i].properties.focused = false;
+
+ var _newFocusedWindow = this.getLastFocused();
+
+ // Fire a new 'blur' event on the window object
+ this[i].dispatchEvent(new OEvent('blur', {
+ browserWindow: _newFocusedWindow
+ }));
+
+ // Fire a new 'blur' event on this manager object
+ this.dispatchEvent(new OEvent('blur', {
+ browserWindow: _newFocusedWindow
+ }));
+
+ // If something is blurring then we should also fire the
+ // corresponding 'focus' events
+
+ // Fire a new 'focus' event on the window object
+ _newFocusedWindow.dispatchEvent(new OEvent('focus', {
+ browserWindow: _prevFocusedWindow
+ }));
+
+ // Fire a new 'focus' event on this manager object
+ this.dispatchEvent(new OEvent('focus', {
+ browserWindow: _prevFocusedWindow
+ }));
+
+ break;
+ }
+
+ }
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+ chrome.windows.onRemoved.addListener(function(windowId) {
+
+ // Remove window from current collection
+ var deleteIndex = -1;
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (this[i].properties.id == windowId) {
+ deleteIndex = i;
+ break;
+ }
+ }
+
+ if (deleteIndex > -1) {
+
+ var removedWindow = this[deleteIndex];
+
+ removedWindow.properties.closed = true;
+
+ // Set window tabs collection to empty
+ removedWindow.tabs.replaceTabs([]);
+
+ // Manually splice the deleteIndex_th_ item from the current windows collection
+ for (var i = deleteIndex, l = this.length; i < l; i++) {
+ if (this[i + 1]) {
+ this[i] = this[i + 1];
+ } else {
+ delete this[i]; // remove last item
+ }
+ }
+ this.length -= 1;
+
+ // Fire a new 'close' event on the closed BrowserWindow object
+ removedWindow.dispatchEvent(new OEvent('close', {}));
+
+ // Fire a new 'close' event on this manager object
+ this.dispatchEvent(new OEvent('close', {
+ 'browserWindow': removedWindow
+ }));
+
+ }
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+};
+
+BrowserWindowManager.prototype = Object.create(OPromise.prototype);
+
+BrowserWindowManager.prototype.create = function(tabsToInject, browserWindowProperties) {
+
+ /*
+ // Support tc-BrowserWindowManager-015 test
+
+ var isEmpty_TabsToInject = true;
+
+ if(tabsToInject && Object.prototype.toString.call(tabsToInject) === "[object Array]") {
+ for(var i = 0, l = tabsToInject.length; i < l; i++) {
+ if( !isObjectEmpty(tabsToInject[i]) ) {
+ isEmpty_TabsToInject = false;
+ break;
+ }
+ }
+ }
+
+ var isEmpty_BrowserWindowProperties = isObjectEmpty(browserWindowProperties || {});
+
+ // undefined/null tabsToInject w/ non-empty window properties is ok
+ if( !isEmpty_BrowserWindowProperties && (tabsToInject === undefined || tabsToInject === null)) {
+ noTabsToInject = false;
+ }
+
+ if(isEmpty_TabsToInject && isEmpty_BrowserWindowProperties) {
+ throw new OError(
+ "NotSupportedError",
+ "Cannot create a new window without providing at least one method parameter.",
+ DOMException.NOT_SUPPORTED_ERR
+ );
+ }
+
+ if(!isEmpty_TabsToInject && isEmpty_BrowserWindowProperties) {
+ throw new OError(
+ "NotSupportedError",
+ "Cannot create a new window without providing at least one window property parameter.",
+ DOMException.NOT_SUPPORTED_ERR
+ );
+ }
+
+ if(isEmpty_TabsToInject && !isEmpty_BrowserWindowProperties) {
+ throw new OError(
+ "NotSupportedError",
+ "Cannot create a new window without providing at least one object (or 'null')",
+ DOMException.NOT_SUPPORTED_ERR
+ );
+ }*/
+
+ // Create new BrowserWindow object (+ sanitize browserWindowProperties values)
+ var shadowBrowserWindow = new BrowserWindow(browserWindowProperties);
+
+ var createProperties = {
+ 'focused': shadowBrowserWindow.properties.focused,
+ 'incognito': shadowBrowserWindow.properties.incognito,
+ 'width': shadowBrowserWindow.properties.width,
+ 'height': shadowBrowserWindow.properties.height,
+ 'top': shadowBrowserWindow.properties.top,
+ 'left': shadowBrowserWindow.properties.left
+ };
+
+ // Add tabs included in the create() call to the newly created
+ // window, if any, based on type
+ var hasTabsToInject = false;
+
+ var tabsToMove = [];
+ var tabsToCreate = [];
+
+ if (tabsToInject &&
+ Object.prototype.toString.call(tabsToInject) === "[object Array]" &&
+ tabsToInject.length > 0) {
+
+ hasTabsToInject = true;
+
+ for (var i = 0, l = tabsToInject.length; i < l; i++) {
+
+ if (tabsToInject[i] instanceof BrowserTab) {
+
+ (function(existingBrowserTab, index) {
+
+ // Remove tab from previous window parent and then
+ // add it to its new window parent
+ if(existingBrowserTab._windowParent) {
+ existingBrowserTab._windowParent.tabs.removeTab(existingBrowserTab);
+ }
+
+ // Rewrite tab's BrowserWindow parent
+ existingBrowserTab._windowParent = shadowBrowserWindow;
+
+ // Rewrite tab's index position in collection
+ existingBrowserTab.properties.index = shadowBrowserWindow.tabs.length;
+
+ shadowBrowserWindow.tabs.addTab( existingBrowserTab, existingBrowserTab.properties.index);
+
+ // Don't create the first tab as this will be resolved differently
+ if(index == 0) {
+
+ // Implicitly add the first BrowserTab to the new window
+ createProperties.tabId = existingBrowserTab.properties.id;
+
+ shadowBrowserWindow.rewriteUrl = newTab_BaseURL.replace('%s', existingBrowserTab.properties.id);
+
+ } else {
+
+ // handled in window.create callback function
+ // because we need the window's id property to move
+ // items to this window object
+ tabsToMove.push(existingBrowserTab);
+
+ }
+
+ // move events etc will fire in onMoved listener of RootBrowserTabManager
+
+ })(tabsToInject[i], i);
+
+ } else { // Treat as a BrowserTabProperties object by default
+
+ (function(browserTabProperties, index) {
+
+ browserTabProperties = browserTabProperties || {};
+
+ var newBrowserTab = new BrowserTab(browserTabProperties, shadowBrowserWindow);
+
+ newBrowserTab.properties.index = i;
+
+ // Register BrowserTab object with the current BrowserWindow object
+ shadowBrowserWindow.tabs.addTab( newBrowserTab, newBrowserTab.properties.index);
+
+ // Add object to root store
+ OEX.tabs.addTab( newBrowserTab );
+
+ // set BrowserWindow object's rewriteUrl to first tab's opera id
+ if( index == 0 ) {
+
+ createProperties.url = shadowBrowserWindow.rewriteUrl = newTab_BaseURL.replace('%s', newBrowserTab._operaId);
+
+ } else {
+
+ tabsToCreate.push(newBrowserTab);
+
+ }
+
+ })(tabsToInject[i], i);
+
+ }
+
+ }
+
+ } else { // we only have one default chrome://newtab or opera://startpage tab to set up
+
+ // setup single new tab and tell onCreated to ignore this item
+ var defaultBrowserTab = new BrowserTab({ active: true }, shadowBrowserWindow);
+
+ // Register BrowserTab object with the current BrowserWindow object
+ shadowBrowserWindow.tabs.addTab( defaultBrowserTab, defaultBrowserTab.properties.index );
+
+ // Add object to root store
+ OEX.tabs.addTab( defaultBrowserTab );
+
+ // set rewriteUrl to windowId
+ shadowBrowserWindow.rewriteUrl = newTab_BaseURL.replace('%s', shadowBrowserWindow._operaId);
+
+ createProperties.url = shadowBrowserWindow.rewriteUrl;
+
+ }
+
+ // Add this object to the current collection
+ this[this.length] = shadowBrowserWindow;
+ this.length += 1;
+
+ // unfocus all other windows in collection if this window is focused
+ if(shadowBrowserWindow.properties.focused == true ) {
+ for(var i = 0, l = this.length; i < l; i++) {
+ if(this[i] !== shadowBrowserWindow) {
+ this[i].properties.focused = false;
+ }
+ }
+ }
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+
+ chrome.windows.create(
+ createProperties,
+ function(_window) {
+
+ // Update BrowserWindow properties
+ for (var i in _window) {
+ if(i == 'tabs') continue; // don't overwrite tabs!
+ shadowBrowserWindow.properties[i] = _window[i];
+ }
+
+ // Move any remaining existing tabs to new window
+ // now that we have the window.id property assigned
+ // above in properties copy
+ if( tabsToMove.length > 0 ) {
+
+ for(var i = 0, l = tabsToMove.length; i < l; i++) {
+
+ (function(existingBrowserTab) {
+
+ // Explicitly move anything after the first BrowserTab to the new window
+ Queue.enqueue(existingBrowserTab, function(done) {
+
+ chrome.tabs.move(
+ this.properties.id,
+ {
+ index: this._windowParent.tabs.length,
+ windowId: this._windowParent.properties.id
+ },
+ function(_tab) {
+ for (var i in _tab) {
+ if(i == 'url') continue;
+ this.properties[i] = _tab[i];
+ }
+
+ done();
+ }.bind(this)
+ );
+ }.bind(existingBrowserTab));
+
+ })(tabsToMove[i]);
+
+ }
+
+ }
+
+ if( tabsToCreate.length > 0 ) {
+
+ for(var i = 0, l = tabsToCreate.length; i < l; i++) {
+
+ (function(newBrowserTab) {
+
+ var tabCreateProps = {
+ 'windowId': shadowBrowserWindow.properties.id,
+ 'url': newBrowserTab.properties.url || 'about:blank',
+ 'active': newBrowserTab.properties.active,
+ 'pinned': newBrowserTab.properties.pinned,
+ 'index': newBrowserTab.properties.index
+ };
+
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.create(
+ tabCreateProps,
+ function(_tab) {
+ for (var i in _tab) {
+ newBrowserTab.properties[i] = _tab[i];
+ }
+
+ newBrowserTab.resolve(true);
+
+ done();
+
+ }.bind(shadowBrowserWindow.tabs)
+ );
+ }.bind(this), true);
+
+ newBrowserTab._windowParent.tabs.dispatchEvent(new OEvent('create', {
+ "tab": newBrowserTab,
+ "prevWindow": newBrowserTab._windowParent,
+ "prevTabGroup": null,
+ "prevPosition": NaN
+ }));
+
+ // Fire a create event at RootTabsManager
+ OEX.tabs.dispatchEvent(new OEvent('create', {
+ "tab": newBrowserTab,
+ "prevWindow": newBrowserTab._windowParent,
+ "prevTabGroup": null,
+ "prevPosition": NaN
+ }));
+
+ })(tabsToCreate[i]);
+
+ }
+
+ }
+
+ done();
+
+ }.bind(this)
+ );
+
+ }.bind(this), true);
+
+ // return shadowBrowserWindow from this function before firing these events!
+ global.setTimeout(function() {
+
+ // Fire a new 'create' event on this manager object
+ this.dispatchEvent(new OEvent('create', {
+ browserWindow: shadowBrowserWindow
+ }));
+
+ }.bind(this), 50);
+
+ return shadowBrowserWindow;
+};
+
+BrowserWindowManager.prototype.getAll = function() {
+
+ var allWindows = [];
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ allWindows[i] = this[i];
+ }
+
+ return allWindows;
+
+};
+
+BrowserWindowManager.prototype.getLastFocused = function() {
+
+ for(var i = 0, l = this.length; i < l; i++) {
+ if(this[i].focused == true) {
+ return this[i];
+ }
+ }
+
+ // default
+ if(this[0]) {
+ this[0].properties.focused = true;
+ }
+
+ return this[0] || undefined;
+
+};
+
+BrowserWindowManager.prototype.close = function(browserWindow) {
+
+ if(!browserWindow || !(browserWindow instanceof BrowserWindow)) {
+ return;
+ }
+
+ browserWindow.close();
+
+};
+
+var BrowserWindow = function(browserWindowProperties) {
+
+ OPromise.call(this);
+
+ browserWindowProperties = browserWindowProperties || {};
+
+ this.properties = {
+ 'id': undefined, // not settable on create
+ 'closed': false, // not settable on create
+ 'focused': browserWindowProperties.focused ? !!browserWindowProperties.focused : undefined,
+ // private:
+ 'incognito': browserWindowProperties.private ? !!browserWindowProperties.private : undefined,
+ 'parent': null,
+ 'width': browserWindowProperties.width ? parseInt(browserWindowProperties.width, 10) : undefined,
+ 'height': browserWindowProperties.height ? parseInt(browserWindowProperties.height, 10) : undefined,
+ 'top': browserWindowProperties.top ? parseInt(browserWindowProperties.top, 10) : undefined,
+ 'left': browserWindowProperties.left ? parseInt(browserWindowProperties.left, 10) : undefined
+ // 'tabGroups' not part of settable properties
+ // 'tabs' not part of settable properties
+ };
+
+ this._parent = null;
+
+ // Create a unique browserWindow id
+ this._operaId = Math.floor(Math.random() * 1e16);
+
+ this.tabs = new BrowserTabManager(this);
+
+ this.tabGroups = new BrowserTabGroupManager(this);
+
+ if(this.properties.private !== undefined) {
+ this.properties.incognito = !!this.properties.private;
+ delete this.properties.private;
+ }
+
+ // Not allowed when creating a new window object
+ if(this.properties.closed !== undefined) {
+ delete this.properties.closed;
+ }
+};
+
+BrowserWindow.prototype = Object.create(OPromise.prototype);
+
+// API
+BrowserWindow.prototype.__defineGetter__("id", function() {
+ return this._operaId;
+});
+
+BrowserWindow.prototype.__defineGetter__("closed", function() {
+ return this.properties.closed !== undefined ? !!this.properties.closed : false;
+});
+
+BrowserWindow.prototype.__defineGetter__("focused", function() {
+ return this.properties.focused !== undefined ? !!this.properties.focused : false;
+});
+
+BrowserWindow.prototype.__defineGetter__("private", function() {
+ return this.properties.incognito !== undefined ? !!this.properties.incognito : false;
+});
+
+BrowserWindow.prototype.__defineGetter__("top", function() {
+ return this.properties.top !== undefined ? this.properties.top : -1;
+}); // read-only
+
+BrowserWindow.prototype.__defineGetter__("left", function() {
+ return this.properties.left !== undefined ? this.properties.left : -1;
+}); // read-only
+
+BrowserWindow.prototype.__defineGetter__("height", function() {
+ return this.properties.height !== undefined ? this.properties.height : -1;
+}); // read-only
+
+BrowserWindow.prototype.__defineGetter__("width", function() {
+ return this.properties.width !== undefined ? this.properties.width : -1;
+}); // read-only
+
+BrowserWindow.prototype.__defineGetter__("parent", function() {
+ return this._parent;
+});
+
+BrowserWindow.prototype.insert = function(browserTab, child) {
+
+ if (!browserTab || !(browserTab instanceof BrowserTab)) {
+ return;
+ }
+
+ if (this.properties.closed === true) {
+ throw new OError(
+ "InvalidStateError",
+ "Current window is in the closed state and therefore is invalid",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ var moveProperties = {
+ windowId: this.properties.id,
+ index: OEX.windows.length // by default, add to the end of the current window
+ };
+
+ // Set insert position for the new tab from 'before' attribute, if any
+ if (child && (child instanceof BrowserTab)) {
+
+ if (child.closed === true) {
+ throw new OError(
+ "InvalidStateError",
+ "'child' parameter is in the closed state and therefore is invalid",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ if (child._windowParent && child._windowParent.closed === true) {
+ throw new OError(
+ "InvalidStateError",
+ "Parent window of 'child' parameter is in the closed state and therefore is invalid",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+ moveProperties.windowId = child._windowParent ?
+ child._windowParent.properties.id : moveProperties.windowId;
+ moveProperties.index = child.position;
+
+ } else {
+ // IF we're moving within the same window then index will be length - 1
+ moveProperties.index = moveProperties.index > 0 ? moveProperties.index - 1 : moveProperties.index;
+ }
+
+ // Detach tab from existing BrowserWindow parent (if any)
+ if (browserTab._windowParent) {
+ browserTab._oldWindowParent = browserTab._windowParent;
+ browserTab._oldIndex = browserTab.properties.index;
+
+ if(browserTab._oldWindowParent !== this) {
+ browserTab._windowParent.tabs.removeTab( browserTab );
+ }
+ }
+
+ // Attach tab to new BrowserWindow parent
+ browserTab._windowParent = this;
+
+ // Update index within new parent
+ browserTab.properties.index = moveProperties.index;
+
+ if(this !== browserTab._oldWindowParent) {
+ // Attach tab to new parent
+ this.tabs.addTab( browserTab, browserTab.properties.index );
+ }
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(browserTab, function(done) {
+ chrome.tabs.move(
+ browserTab.properties.id,
+ {
+ windowId: moveProperties.windowId || this.properties.id,
+ index: moveProperties.index
+ },
+ function(_tab) {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+BrowserWindow.prototype.focus = function() {
+
+ if(this.properties.focused == true || this.properties.closed == true) {
+ return; // already focused or invalid because window is closed
+ }
+
+ // Set BrowserWindow object to focused state
+ this.properties.focused = true;
+
+ // unset all other window object's focused state
+ for(var i = 0, l = OEX.windows.length; i < l; i++) {
+ if(OEX.windows[i] !== this) {
+ OEX.windows[i].properties.focused = false;
+ }
+ }
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.windows.update(
+ this.properties.id,
+ { focused: true },
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+BrowserWindow.prototype.update = function(browserWindowProperties) {
+
+ var updateProperties = {};
+
+ if(browserWindowProperties.focused !== undefined && browserWindowProperties.focused == true) {
+ this.properties.focused = updateProperties.focused = !!browserWindowProperties.focused;
+ }
+
+ if(browserWindowProperties.top !== undefined && browserWindowProperties.top !== null) {
+ this.properties.top = updateProperties.top = parseInt(browserWindowProperties.top, 10);
+ }
+
+ if(browserWindowProperties.left !== undefined && browserWindowProperties.left !== null) {
+ this.properties.left = updateProperties.left = parseInt(browserWindowProperties.left, 10);
+ }
+
+ if(browserWindowProperties.height !== undefined && browserWindowProperties.height !== null) {
+ this.properties.height = updateProperties.height = parseInt(browserWindowProperties.height, 10);
+ }
+
+ if(browserWindowProperties.width !== undefined && browserWindowProperties.width !== null) {
+ this.properties.width = updateProperties.width = parseInt(browserWindowProperties.width, 10);
+ }
+
+ if( !isObjectEmpty(updateProperties) ) {
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.windows.update(
+ this.properties.id,
+ updateProperties,
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+ }
+
+}
+
+BrowserWindow.prototype.close = function() {
+
+ if( this.properties.closed == true) {
+ /*throw new OError(
+ "InvalidStateError",
+ "The current BrowserWindow object is already closed. Cannot call close on this object.",
+ DOMException.INVALID_STATE_ERR
+ );*/
+ return;
+ }
+
+ // Set BrowserWindow object to closed state
+ this.properties.closed = true;
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ if(!this.properties.id) return;
+ chrome.windows.remove(
+ this.properties.id,
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+var BrowserTabManager = function( parentObj ) {
+
+ OPromise.call( this );
+
+ // Set up 0 mock BrowserTab objects at startup
+ this.length = 0;
+
+ this._parent = parentObj;
+
+ // Remove all collection items and replace with browserTabs
+ this.replaceTabs = function( browserTabs ) {
+
+ for( var i = 0, l = this.length; i < l; i++ ) {
+ delete this[ i ];
+ }
+ this.length = 0;
+
+ if(browserTabs.length <= 0) {
+ return;
+ }
+
+ for( var i = 0, l = browserTabs.length; i < l; i++ ) {
+ if(this !== OEX.tabs) {
+ browserTabs[ i ].properties.index = i;
+ }
+ this[ i ] = browserTabs[ i ];
+ }
+ this.length = browserTabs.length;
+
+ // Set focused on first tab object, unless some other tab object has focused=='true'
+ var focusFound = false;
+ for(var i = 0, l = browserTabs.length; i < l; i++) {
+ if(browserTabs[i].properties.active == true) {
+ focusFound = true;
+ break;
+ }
+ }
+ if(!focusFound) {
+ browserTabs[0].focus();
+ }
+
+ };
+
+ // Add an array of browserTabs to the current collection
+ this.addTab = function( browserTab, startPosition ) {
+ // Extract current set of tabs in collection
+ var allTabs = [];
+
+ for(var i = 0, l = this.length; i < l; i++) {
+ allTabs[ i ] = this[ i ];
+ if(allTabs[ i ].properties.active == true) {
+ focusFound = true;
+ }
+ }
+
+ if(browserTab.properties.active == true) {
+ browserTab.focus();
+ }
+
+ var position = startPosition !== undefined ? startPosition : allTabs.length;
+
+ // Add new browserTab to allTabs array
+ allTabs.splice(this !== OEX.tabs ? position : this.length, 0, browserTab);
+
+ // Rewrite the current tabs collection in order
+ for( var i = 0, l = allTabs.length; i < l; i++ ) {
+ if(this !== OEX.tabs) {
+ // Update all tab indexes to the current tabs collection order
+ allTabs[ i ].properties.index = i;
+ }
+ this[ i ] = allTabs[ i ];
+ }
+ this.length = allTabs.length;
+
+ };
+
+ // Remove a browserTab from the current collection
+ this.removeTab = function( browserTab ) {
+
+ var oldCollectionLength = this.length;
+
+ // Extract current set of tabs in collection
+ var allTabs = [];
+ var removeTabIndex = -1;
+ for(var i = 0, l = this.length; i < l; i++) {
+ allTabs[ i ] = this[ i ];
+ if( allTabs[ i ].id == browserTab.id ) {
+ removeTabIndex = i;
+ }
+ }
+
+ // Remove browser tab
+ if(removeTabIndex > -1) {
+ allTabs.splice(removeTabIndex, 1);
+ }
+
+ // Rewrite the current tabs collection
+ for( var i = 0, l = allTabs.length; i < l; i++ ) {
+ if(this !== OEX.tabs) {
+ allTabs[ i ].properties.index = i;
+ }
+ this[ i ] = allTabs[ i ];
+ }
+ this.length = allTabs.length;
+
+ // Remove any ghost items, if any
+ if(oldCollectionLength > this.length) {
+ for(var i = this.length, l = oldCollectionLength; i < l; i++) {
+ delete this[ i ];
+ }
+ }
+
+ };
+
+};
+
+BrowserTabManager.prototype = Object.create( OPromise.prototype );
+
+BrowserTabManager.prototype.create = function( browserTabProperties, before ) {
+
+ if(before && !(before instanceof BrowserTab)) {
+ throw new OError(
+ "TypeMismatchError",
+ "Could not create BrowserTab object. 'before' attribute provided is invalid.",
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ } else if(before) {
+
+ if( before.closed === true ) {
+ throw new OError(
+ "InvalidStateError",
+ "'before' BrowserTab object is in the closed state and therefore is invalid.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ if(before._windowParent && before._windowParent.closed === true ) {
+ throw new OError(
+ "InvalidStateError",
+ "Parent window of 'before' BrowserTab object is in the closed state and therefore is invalid.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ // If we're adding this BrowserTab before an existing object then set its insert position correctly
+ browserTabProperties.position = before.properties.index;
+
+ }
+
+ // Set parent window to create the tab in
+ var windowParent = before && before._windowParent ? before._windowParent : this._parent || OEX.windows.getLastFocused();
+
+ if(windowParent && windowParent.closed === true ) {
+ throw new OError(
+ "InvalidStateError",
+ "Parent window of the current BrowserTab object is in the closed state and therefore is invalid.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ var shadowBrowserTab = new BrowserTab( browserTabProperties, windowParent );
+
+ // Sanitized tab properties
+ var createTabProperties = {
+ 'url': shadowBrowserTab.properties.url,
+ 'active': shadowBrowserTab.properties.active,
+ 'pinned': shadowBrowserTab.properties.pinned,
+ 'index': shadowBrowserTab.properties.index
+ };
+
+ // Set insert position for the new tab from 'before' attribute, if any
+ if( before ) {
+ createTabProperties.windowId = before._windowParent ?
+ before._windowParent.properties.id : createTabProperties.windowId;
+ }
+
+ // Add this object to the end of the current tabs collection
+ shadowBrowserTab._windowParent.tabs.addTab(shadowBrowserTab, shadowBrowserTab.properties.index);
+
+ // unfocus all other tabs in tab's window parent collection if this tab is set to focused
+ if(shadowBrowserTab.properties.active == true ) {
+ for(var i = 0, l = shadowBrowserTab._windowParent.tabs.length; i < l; i++) {
+ if(shadowBrowserTab._windowParent.tabs[i] !== shadowBrowserTab) {
+ shadowBrowserTab._windowParent.tabs[i].properties.active = false;
+ }
+ }
+ }
+
+ // Add this object to the root tab manager
+ OEX.tabs.addTab( shadowBrowserTab );
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+
+ chrome.tabs.create(
+ createTabProperties,
+ function( _tab ) {
+ // Update BrowserTab properties
+ for(var i in _tab) {
+ if(i == 'url') continue;
+ shadowBrowserTab.properties[i] = _tab[i];
+ }
+
+ // Resolve new tab, if it hasn't been resolved already
+ shadowBrowserTab.resolve(true);
+
+ done();
+
+ }.bind(this)
+ );
+
+ }.bind(this), true);
+
+ // return shadowBrowserTab from this function before firing these events!
+ global.setTimeout(function() {
+
+ shadowBrowserTab._windowParent.tabs.dispatchEvent(new OEvent('create', {
+ "tab": shadowBrowserTab,
+ "prevWindow": null,
+ "prevTabGroup": null,
+ "prevPosition": 0
+ }));
+
+ // Fire a create event at RootTabsManager
+ OEX.tabs.dispatchEvent(new OEvent('create', {
+ "tab": shadowBrowserTab,
+ "prevWindow": null,
+ "prevTabGroup": null,
+ "prevPosition": 0
+ }));
+
+ }, 50);
+
+ return shadowBrowserTab;
+
+};
+
+BrowserTabManager.prototype.getAll = function() {
+
+ var allTabs = [];
+
+ for(var i = 0, l = this.length; i < l; i++) {
+ allTabs[ i ] = this[ i ];
+ }
+
+ return allTabs;
+
+};
+
+BrowserTabManager.prototype.getSelected = function() {
+
+ for(var i = 0, l = this.length; i < l; i++) {
+ if(this[i].focused == true) {
+ return this[i];
+ }
+ }
+
+ // default
+ if(this[0]) {
+ this[0].properties.active = true;
+ }
+
+ return this[0] || undefined;
+
+};
+// Alias of .getSelected()
+BrowserTabManager.prototype.getFocused = BrowserTabManager.prototype.getSelected;
+
+BrowserTabManager.prototype.close = function( browserTab ) {
+
+ if( !browserTab || !(browserTab instanceof BrowserTab)) {
+ throw new OError(
+ "TypeMismatchError",
+ "Expected browserTab argument to be of type BrowserTab.",
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ }
+
+ browserTab.close();
+
+};
+
+var RootBrowserTabManager = function() {
+
+ BrowserTabManager.call(this);
+
+ // list of tab objects we should ignore
+ this._blackList = {};
+
+ // global permanaent tabs collection manager
+ this._allTabs = [];
+
+ // Event Listener implementations
+ chrome.tabs.onCreated.addListener(function(_tab) {
+
+ var tabFoundIndex = -1;
+
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+
+ // opera.extension.windows.create rewrite hack
+ if (this._allTabs[i].rewriteUrl && this._allTabs[i].properties.url == _tab.url) {
+
+ if(this._allTabs[i]._windowParent) {
+
+ // If the window ids don't match then silently move the tab to the correct parent
+ // e.g. this happens if we create a new tab from the background page's console.
+ if(this._allTabs[i]._windowParent.properties.id !== _tab.windowId) {
+ for(var j = 0, k = OEX.windows.length; j < k; j++) {
+ if(OEX.windows[j].properties.id == _tab.windowId) {
+ this._allTabs[i]._windowParent.tabs.removeTab(this._allTabs[i]);
+ this._allTabs[i]._windowParent = OEX.windows[j];
+ //this._allTabs[i].properties.index = this._allTabs[i]._windowParent.tabs.length;
+ this._allTabs[i].properties.windowId = _tab.windowId;
+
+ // Force change tab's index position in platform
+ Queue.enqueue(this._allTabs[i], function(done) {
+ chrome.tabs.move(
+ this.properties.id,
+ { index: this._windowParent.tabs.length },
+ function(_tab) {
+ done();
+ }.bind(this)
+ );
+ }.bind(this._allTabs[i]));
+
+ OEX.windows[j].tabs.addTab( this._allTabs[i], this._allTabs[i].properties.index);
+ }
+ }
+ }
+
+ // Resolve the parent window object, if it's not already resolved
+ this._allTabs[i]._windowParent.properties.id = _tab.windowId;
+ this._allTabs[i]._windowParent.resolve(true);
+ // Also resolve window object's root tab manager
+ this._allTabs[i]._windowParent.tabs.resolve(true);
+
+ } else {
+
+ throw new OError('NoParent', 'BrowserTab object must have a parent window.');
+
+ }
+
+ // Rewrite tab properties (importantly, the id gets added here)
+ /*for(var j in _tab) {
+ if(j == 'url') continue;
+ this._allTabs[i].properties[j] = _tab[j];
+ }*/
+ // update oncreate tab properties
+ this._allTabs[i].properties.id = _tab.id;
+ this._allTabs[i].properties.index = _tab.index;
+
+ /*this._allTabs[i].properties.status = _tab.readyState;
+ this._allTabs[i].properties.title = _tab.title;
+ this._allTabs[i].properties.favIconUrl = _tab.favIconUrl;
+ this._allTabs[i].properties.incognito = _tab.incognito;
+ this._allTabs[i].properties.pinned = _tab.pinned;*/
+ // 'index' should be handled in shim
+
+ // now rewrite tab to the correct url
+ // (which will be automatically trigger navigation to the rewrite url)
+
+ // Resolve the tab object
+ this._allTabs[i].resolve(true);
+
+ // remove windowparent rewrite url
+ if(this._allTabs[i]._windowParent.rewriteUrl !== undefined) {
+ delete this._allTabs[i]._windowParent.rewriteUrl;
+ }
+
+ return;
+ }
+
+ // Standard tab search
+ if (this._allTabs[i].properties.id == _tab.id) {
+ tabFoundIndex = i;
+ break;
+ }
+ }
+
+ var newTab;
+
+ if (tabFoundIndex < 0) {
+
+ var parentWindow;
+
+ // find tab's parent window object via the window.rewriteURL property
+ var _windows = OEX.windows;
+ for (var i = 0, l = _windows.length; i < l; i++) {
+
+ // Bind the window object with its window id and resolve
+ if( _windows[i].rewriteUrl && _windows[i].rewriteUrl == _tab.url ) {
+ _windows[i].properties.id = _tab.windowId;
+ _windows[i].resolve(true);
+ // Also resolve window object's root tab manager
+ _windows[i].tabs.resolve(true);
+ }
+
+ if (_windows[i].properties.id !== undefined && _windows[i].properties.id == _tab.windowId) {
+ parentWindow = _windows[i];
+ break;
+ }
+ }
+
+ if (!parentWindow) {
+
+ // Create new BrowserWindow object
+ parentWindow = new BrowserWindow();
+
+ // write properties not available in BrowserWindow constructor
+ parentWindow.properties.id = _tab.windowId;
+
+ // Attach to windows collection
+ OEX.windows.addWindow(_tab.windowId, parentWindow);
+
+ parentWindow.resolve(true);
+ parentWindow.tabs.resolve(true);
+
+ // we really need to learn more about the newly create BrowserWindow object
+ chrome.windows.get(parentWindow.properties.id, { 'populate': false }, function(_window) {
+
+ // update window properties
+ for(var prop in _window) {
+ if(prop == 'tabs') continue;
+ parentWindow.properties[prop] = _window[prop];
+ }
+
+ }.bind(this));
+
+ }
+
+ // Replace first tab object with newTab
+ if(parentWindow.rewriteUrl && parentWindow.tabs.length > 0) {
+
+ newTab = parentWindow.tabs[0];
+
+ // rewrite the tab's properties
+ for(var j in _tab) {
+ newTab.properties[j] = _tab[j];
+ }
+
+ } else {
+
+ // Create the new BrowserTab object using the provided properties
+ newTab = new BrowserTab(_tab, parentWindow, true);
+
+ // write properties not available in BrowserTab constructor
+ newTab.properties.id = _tab.id;
+ newTab.properties.url = _tab.url;
+ newTab.properties.title = _tab.title;
+ newTab.properties.favIconUrl = _tab.favIconUrl;
+
+ newTab.properties.pinned = _tab.pinned;
+ newTab.properties.incognito = _tab.incognito;
+
+ newTab.properties.status = _tab.status;
+
+ newTab.properties.index = _tab.index;
+
+ if(_tab.active == true && newTab.properties.active == false) {
+ newTab.focus();
+ }
+
+ // Register the new BrowserTab object with a BrowserWindow's tabs collection
+ newTab._windowParent.tabs.addTab( newTab, newTab.properties.index );
+
+ // Add object to root store
+ this.addTab( newTab );
+
+ }
+
+ // Fire create events for a newly created BrowserTab object
+ newTab._windowParent.tabs.dispatchEvent(new OEvent('create', {
+ "tab": newTab,
+ "prevWindow": null,
+ "prevTabGroup": null,
+ "prevPosition": 0
+ }));
+
+ // Fire a create event at RootTabsManager
+ OEX.tabs.dispatchEvent(new OEvent('create', {
+ "tab": newTab,
+ "prevWindow": null,
+ "prevTabGroup": null,
+ "prevPosition": 0
+ }));
+
+ } else {
+
+ newTab = this[tabFoundIndex];
+
+ // Update existing tab properties
+ for(var i in _tab) {
+ if(i == 'url') continue;
+ newTab.properties[i] = _tab[i];
+ }
+ // update individual properties
+ //newTab.properties.id = _tab.id;
+
+ }
+
+ // remove window rewriteUrl since the bootstrap has now been used
+ if(newTab._windowParent.rewriteUrl !== undefined) {
+ delete newTab._windowParent.rewriteUrl;
+ }
+
+ // Resolve new tab, if it hasn't been resolved already
+ newTab.resolve(true);
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+ chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
+
+ if( this._blackList[ tabId ] ) {
+ return;
+ }
+
+ // Remove tab from current collection
+ var deleteIndex = -1;
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+ if (this._allTabs[i].properties.id == tabId) {
+ deleteIndex = i;
+ break;
+ }
+ }
+
+ if (deleteIndex < 0) {
+ return;
+ }
+
+ var oldTab = this._allTabs[deleteIndex];
+
+ var oldTabWindowParent = oldTab._oldWindowParent;
+ var oldTabPosition = oldTab._oldIndex || oldTab.properties.index;
+
+ // Detach from current parent BrowserWindow (if close happened outside of our framework)
+ if (!oldTabWindowParent && oldTab._windowParent !== undefined && oldTab._windowParent !== null) {
+ oldTab.properties.closed = true;
+
+ oldTab._windowParent.tabs.removeTab( oldTab );
+
+ // Remove tab from root tab manager
+ this.removeTab( oldTab );
+
+ // Focus new tab within the removed tab's window
+ for(var i = 0, l = oldTab._windowParent.tabs.length; i < l; i++) {
+ if(oldTab._windowParent.tabs[i].properties.active == true) {
+ oldTab._windowParent.tabs[i].focus();
+ } else {
+ oldTab._windowParent.tabs[i].properties.active = false;
+ }
+ }
+
+ oldTab.properties.index = NaN;
+
+ oldTabWindowParent = oldTab._windowParent;
+ oldTab._windowParent = null;
+ }
+
+ // Fire a new 'close' event on the closed BrowserTab object
+ oldTab.dispatchEvent(new OEvent('close', {
+ "tab": oldTab,
+ "prevWindow": oldTabWindowParent,
+ "prevTabGroup": null,
+ "prevPosition": oldTabPosition
+ }));
+
+ // Fire a new 'close' event on the closed BrowserTab's previous
+ // BrowserWindow parent object
+ if(oldTabWindowParent) {
+
+ oldTabWindowParent.tabs.dispatchEvent(new OEvent('close', {
+ "tab": oldTab,
+ "prevWindow": oldTabWindowParent,
+ "prevTabGroup": null,
+ "prevPosition": oldTabPosition
+ }));
+
+ }
+
+ // Fire a new 'close' event on this root tab manager object
+ this.dispatchEvent(new OEvent('close', {
+ "tab": oldTab,
+ "prevWindow": oldTabWindowParent,
+ "prevTabGroup": null,
+ "prevPosition": oldTabPosition
+ }));
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+
+ var updateIndex = -1;
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+ if (this._allTabs[i].properties.id == tabId) {
+ updateIndex = i;
+ break;
+ }
+ }
+
+ if (updateIndex < 0) {
+ return; // nothing to update
+ }
+
+ var updateTab = this._allTabs[updateIndex];
+
+ if (tab.status == 'complete' && updateTab.rewriteUrl && updateTab.properties.url == tab.url) {
+
+ updateTab.properties.url = updateTab.rewriteUrl;
+ updateTab.properties.title = '';
+ updateTab.properties.favIconUrl = '';
+ updateTab.properties.status = 'loading';
+
+ delete updateTab.rewriteUrl;
+
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.update(
+ updateTab.properties.id,
+ { 'url': updateTab.properties.url },
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+ } else {
+
+ // Update individual tab properties
+ updateTab.properties.url = tab.url;
+ updateTab.properties.title = tab.title;
+ updateTab.properties.favIconUrl = tab.favIconUrl;
+
+ updateTab.properties.status = tab.status;
+
+ updateTab.properties.pinned = tab.pinned;
+ updateTab.properties.incognito = tab.incognito;
+
+ updateTab.properties.index = tab.index;
+
+ if(tab.active == true && updateTab.properties.active == false) {
+ updateTab.focus();
+ }
+
+ }
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+ function moveHandler(tabId, moveInfo) {
+
+ if( this._blackList[ tabId ] ) {
+ return;
+ }
+
+ // find tab's parent window object via the window.rewriteURL property
+ // and rewrite it's id value
+ var _windows = OEX.windows;
+ for (var i = 0, l = _windows.length; i < l; i++) {
+
+ // Bind the window object with its window id and resolve
+ if( _windows[i].rewriteUrl && _windows[i].rewriteUrl == newTab_BaseURL.replace('%s', tabId) ) {
+ _windows[i].properties.id = moveInfo.windowId;
+ _windows[i].resolve(true);
+ // Also resolve window object's root tab manager
+ _windows[i].tabs.resolve(true);
+
+ delete _windows[i].rewriteUrl;
+ }
+ }
+
+ // Find tab object
+ var moveIndex = -1;
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+ if (this._allTabs[i].properties.id == tabId) {
+ moveIndex = i;
+ break;
+ }
+ }
+
+ if (moveIndex < 0) {
+ return; // nothing to update
+ }
+
+ var moveTab = this._allTabs[moveIndex];
+
+ if(moveTab) {
+
+ // Remove and re-add to BrowserTabManager parent in the correct position
+ if(moveTab._oldWindowParent) {
+ moveTab._oldWindowParent.tabs.removeTab( moveTab );
+ } else {
+ moveTab._windowParent.tabs.removeTab( moveTab );
+ }
+
+ // Update index
+ moveTab.properties.index = moveInfo.toIndex;
+
+ moveTab._windowParent.tabs.addTab( moveTab, moveInfo.toIndex );
+
+ moveTab.dispatchEvent(new OEvent('move', {
+ "tab": moveTab,
+ "prevWindow": moveTab._oldWindowParent,
+ "prevTabGroup": null,
+ "prevPosition": moveTab._oldIndex
+ }));
+
+ this.dispatchEvent(new OEvent('move', {
+ "tab": moveTab,
+ "prevWindow": moveTab._oldWindowParent,
+ "prevTabGroup": null,
+ "prevPosition": moveTab._oldIndex
+ }));
+
+ // Clean up temporary attributes
+ if(moveTab._oldWindowParent !== undefined) {
+ delete moveTab._oldWindowParent;
+ }
+ if(moveTab._oldIndex !== undefined) {
+ delete moveTab._oldIndex;
+ }
+
+ }
+
+// Queue.dequeue();
+
+ }
+
+ function attachHandler(tabId, attachInfo) {
+
+ // Find tab object
+ var attachIndex = -1;
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+ if (this._allTabs[i].properties.id == tabId) {
+ attachIndex = i;
+ break;
+ }
+ }
+
+ if (attachIndex < 0) {
+ return; // nothing to update
+ }
+
+ var attachedTab = this._allTabs[attachIndex];
+
+ // Detach tab from existing BrowserWindow parent (if any)
+ if (attachedTab._oldWindowParent) {
+ attachedTab._oldWindowParent.tabs.removeTab( attachedTab );
+ }
+
+ // Wait for new window to be created and attached!
+ //global.setTimeout(function() {
+
+ // Attach tab to new BrowserWindow parent
+ for (var i = 0, l = OEX.windows.length; i < l; i++) {
+ if (OEX.windows[i].properties.id == attachInfo.newWindowId) {
+ // Reassign attachedTab's _windowParent
+ attachedTab._windowParent = OEX.windows[i];
+
+ // Tab will be added in the moveHandler function
+
+ break;
+ }
+ }
+
+ var moveInfo = {
+ windowId: attachInfo.newWindowId,
+ //fromIndex: null,
+ toIndex: attachInfo.newPosition
+ };
+
+ // Execute normal move handler
+ moveHandler.bind(this).call(this, tabId, moveInfo);
+
+ //}.bind(this), 200);
+ }
+
+ function detachHandler(tabId, detachInfo) {
+
+ // Find tab object
+ var detachIndex = -1;
+ for (var i = 0, l = this._allTabs.length; i < l; i++) {
+ if (this._allTabs[i].properties.id == tabId) {
+ detachIndex = i;
+ break;
+ }
+ }
+
+ if (detachIndex < 0) {
+ return; // nothing to update
+ }
+
+ var detachedTab = this._allTabs[detachIndex];
+
+ if(detachedTab) {
+ detachedTab._oldWindowParent = detachedTab._windowParent;
+ detachedTab._oldIndex = detachedTab.position;
+ }
+
+// Queue.dequeue();
+
+ }
+
+ // Fired when a tab is moved within a window
+ chrome.tabs.onMoved.addListener(moveHandler.bind(this));
+
+ // Fired when a tab is moved between windows
+ chrome.tabs.onAttached.addListener(attachHandler.bind(this));
+
+ // Fired when a tab is removed from an existing window
+ chrome.tabs.onDetached.addListener(detachHandler.bind(this));
+
+ chrome.tabs.onActivated.addListener(function(activeInfo) {
+
+ if( this._blackList[ activeInfo.tabId ] ) {
+ return;
+ }
+
+ if(!activeInfo.tabId) return;
+
+ var blurTarget, focusTarget;
+
+ for(var i = 0, l = this._allTabs.length; i < l; i++) {
+
+ if(this._allTabs[i].properties.id == activeInfo.tabId) {
+
+ // Set BrowserTab object to active state
+ this._allTabs[i].properties.active = true;
+
+ // Fire focus event on tab's manager
+ focusTarget = this._allTabs[i];
+
+ if(this._allTabs[i]._windowParent) {
+
+ // unset active state of all other tabs in this collection
+ for(var j = 0, k = this._allTabs[i]._windowParent.tabs.length; j < k; j++) {
+ if(this._allTabs[i]._windowParent.tabs[j] !== this._allTabs[i]) {
+ if(this._allTabs[i]._windowParent.tabs[j].properties.active == true) {
+ blurTarget = this._allTabs[i]._windowParent.tabs[j];
+ }
+ this._allTabs[i]._windowParent.tabs[j].properties.active = false;
+ }
+ }
+ }
+
+ }
+
+ }
+
+ // Fire blur event
+ if(focusTarget) {
+ OEX.tabs.dispatchEvent( new OEvent('blur', {
+ "tab": focusTarget,
+ "prevWindow": focusTarget._windowParent, // same as current window
+ "prevTabGroup": null,
+ "prevPosition": focusTarget.properties.index
+ }) );
+ }
+
+ // Fire focus event
+ if(blurTarget) {
+ OEX.tabs.dispatchEvent( new OEvent('focus', {
+ "tab": blurTarget,
+ "prevWindow": blurTarget._windowParent, // same as current window
+ "prevTabGroup": null,
+ "prevPosition": blurTarget.properties.index
+ }) );
+ }
+
+// Queue.dequeue();
+
+ }.bind(this));
+
+ // Listen for getScreenshot requests from Injected Scripts
+ OEX.addEventListener('controlmessage', function( msg ) {
+
+ if( !msg.data || !msg.data.action || msg.data.action !== '___O_getScreenshot_REQUEST' || !msg.source.tabId ) {
+ return;
+ }
+
+ // Resolve tabId to BrowserTab object
+ var sourceBrowserTab = null
+ for(var i = 0, l = this._allTabs.length; i < l; i++) {
+ if( this._allTabs[ i ].properties.id == msg.source.tabId ) {
+ sourceBrowserTab = this._allTabs[ i ];
+ break;
+ }
+ }
+
+ if( sourceBrowserTab !== null &&
+ sourceBrowserTab._windowParent &&
+ sourceBrowserTab._windowParent.properties.closed != true ) {
+
+ try {
+
+ // Get screenshot of requested window belonging to current tab
+ chrome.tabs.captureVisibleTab(
+ sourceBrowserTab._windowParent.properties.id,
+ {},
+ function( nativeCallback ) {
+
+ // Return the result to the callee
+ msg.source.postMessage({
+ "action": "___O_getScreenshot_RESPONSE",
+ "dataUrl": nativeCallback || null
+ });
+
+ }.bind(this)
+ );
+
+ } catch(e) {}
+
+ } else {
+
+ msg.source.postMessage({
+ "action": "___O_getScreenshot_RESPONSE",
+ "dataUrl": undefined
+ });
+
+ }
+
+ }.bind(this));
+
+};
+
+RootBrowserTabManager.prototype = Object.create( BrowserTabManager.prototype );
+
+// Make sure .__proto__ object gets setup correctly
+for(var i in BrowserTabManager.prototype) {
+ if(BrowserTabManager.prototype.hasOwnProperty(i)) {
+ RootBrowserTabManager.prototype[i] = BrowserTabManager.prototype[i];
+ }
+}
+
+
+var BrowserTab = function(browserTabProperties, windowParent, bypassRewriteUrl) {
+
+ if(!windowParent) {
+ throw new OError('Parent missing', 'BrowserTab objects can only be created with a window parent provided');
+ }
+
+ OPromise.call(this);
+
+ this._windowParent = windowParent;
+
+ browserTabProperties = browserTabProperties || {};
+
+ // Set the correct tab index
+ var tabIndex = 0;
+ if(browserTabProperties.position !== undefined &&
+ browserTabProperties.position !== null &&
+ parseInt(browserTabProperties.position, 10) >= 0) {
+ tabIndex = parseInt(browserTabProperties.position, 10);
+ } else if(windowParent && windowParent.tabs) {
+ tabIndex = windowParent.tabs.length;
+ }
+
+ this.properties = {
+ 'id': undefined, // not settable on create
+ 'closed': false, // not settable on create
+ // locked:
+ 'pinned': browserTabProperties.locked ? !!browserTabProperties.locked : false,
+ // private:
+ 'incognito': false, // TODO handle private tab creation in Chromium model
+ // selected:
+ 'active': browserTabProperties.focused ? !!browserTabProperties.focused : false,
+ // readyState:
+ 'status': 'loading', // not settable on create
+ // faviconUrl:
+ 'favIconUrl': '', // not settable on create
+ 'title': '', // not settable on create
+ 'url': browserTabProperties.url ? (browserTabProperties.url + "") : 'about:blank',
+ // position:
+ 'index': tabIndex
+ // 'browserWindow' not part of settable properties
+ // 'tabGroup' not part of settable properties
+ }
+
+ // Create a unique browserTab id
+ this._operaId = Math.floor(Math.random() * 1e16);
+
+ // Pass the identity of this tab through the Chromium Tabs API via the URL field
+ if(!bypassRewriteUrl) {
+ this.rewriteUrl = this.properties.url;
+ this.properties.url = newTab_BaseURL.replace('%s', this._operaId)
+ }
+
+ // Set tab focused if active
+ if(this.properties.active == true) {
+ this.focus();
+ }
+
+ // Add this object to the permanent management collection
+ OEX.tabs._allTabs.push( this );
+
+};
+
+BrowserTab.prototype = Object.create(OPromise.prototype);
+
+// API
+BrowserTab.prototype.__defineGetter__("id", function() {
+ return this._operaId;
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("closed", function() {
+ return this.properties.closed !== undefined ? !!this.properties.closed : false;
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("locked", function() {
+ return this.properties.pinned !== undefined ? !!this.properties.pinned : false;
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("focused", function() {
+ return this.properties.active !== undefined ? !!this.properties.active : false;
+}); // read
+
+BrowserTab.prototype.__defineSetter__("focused", function(val) {
+ this.properties.active = !!val;
+
+ if(this.properties.active == true) {
+ this.focus();
+ }
+}); // write
+
+BrowserTab.prototype.__defineGetter__("selected", function() {
+ return this.properties.active !== undefined ? !!this.properties.active : false;
+}); // read
+
+BrowserTab.prototype.__defineSetter__("selected", function(val) {
+ this.properties.active = !!val;
+
+ if(this.properties.active == true) {
+ this.focus();
+ }
+}); // write
+
+BrowserTab.prototype.__defineGetter__("private", function() {
+ return this.properties.incognito !== undefined ? !!this.properties.incognito : false;
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("faviconUrl", function() {
+ if (this.properties.closed) {
+ return "";
+ }
+ return this.properties.favIconUrl || "";
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("title", function() {
+ if (this.properties.closed) {
+ return "";
+ }
+ return this.properties.title || "";
+}); // read-only
+
+BrowserTab.prototype.__defineGetter__("url", function() {
+ if (this.properties.closed) {
+ return "";
+ }
+
+ // URL rewrite hack
+ if (this.rewriteUrl) {
+ return this.rewriteUrl;
+ }
+
+ return this.properties.url || "";
+}); // read-only
+
+BrowserTab.prototype.__defineSetter__("url", function(val) {
+ this.properties.url = val + "";
+
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.update(
+ this.properties.id,
+ { 'url': this.properties.url },
+ function(_tab) {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+});
+
+BrowserTab.prototype.__defineGetter__("readyState", function() {
+ return this.properties.status !== undefined ? this.properties.status : "loading";
+});
+
+BrowserTab.prototype.__defineGetter__("browserWindow", function() {
+ return this._windowParent;
+});
+
+BrowserTab.prototype.__defineGetter__("tabGroup", function() {
+ // not implemented
+ return null;
+});
+
+BrowserTab.prototype.__defineGetter__("position", function() {
+ return this.properties.index !== undefined ? this.properties.index : NaN;
+});
+
+// Methods
+
+BrowserTab.prototype.focus = function() {
+
+ if(this.properties.active == true || this.properties.closed == true) {
+ return; // already focused or invalid because tab is closed
+ }
+
+ // Set BrowserTab object to active state
+ this.properties.active = true;
+
+ if(this._windowParent) {
+ // unset active state of all other tabs in this collection
+ for(var i = 0, l = this._windowParent.tabs.length; i < l; i++) {
+ if(this._windowParent.tabs[i] !== this) {
+ this._windowParent.tabs[i].properties.active = false;
+ }
+ }
+ }
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.update(
+ this.properties.id,
+ { active: true },
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+BrowserTab.prototype.update = function(browserTabProperties) {
+
+ if( this.properties.closed == true ) {
+ throw new OError(
+ "InvalidStateError",
+ "The current BrowserTab object is closed. Cannot call 'update' on this object.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ if( isObjectEmpty(browserTabProperties || {}) ) {
+ throw new OError(
+ 'TypeMismatchError',
+ 'You must provide some valid properties to update a BrowserTab object',
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ }
+
+ var updateProperties = {};
+
+ // Cannot set focused = false in update
+ if(browserTabProperties.focused !== undefined && browserTabProperties.focused == true) {
+ this.properties.active = updateProperties.active = !!browserTabProperties.focused;
+
+ // unset active parameter of all other objects
+ if(this._windowParent) {
+ for(var i = 0, l = this._windowParent.tabs.length; i < l; i++) {
+ if(this._windowParent.tabs[i] != this) {
+ this._windowParent.tabs[i].properties.active = false;
+ }
+ }
+ }
+ }
+
+ if(browserTabProperties.locked !== undefined && browserTabProperties.locked !== null) {
+ this.properties.pinned = updateProperties.pinned = !!browserTabProperties.locked;
+ }
+
+ if(browserTabProperties.url !== undefined && browserTabProperties.url !== null) {
+ if(this.rewriteUrl) {
+ this.rewriteUrl = updateProperties.url = browserTabProperties.url;
+ } else {
+ this.properties.url = updateProperties.url = browserTabProperties.url;
+ }
+ }
+
+ if( !isObjectEmpty(updateProperties) ) {
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.update(
+ this.properties.id,
+ updateProperties,
+ function(_tab) {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+ }
+
+};
+
+BrowserTab.prototype.refresh = function() {
+
+ // Cannot refresh if the tab is in the closed state
+ if(this.properties.closed === true) {
+ return;
+ }
+
+ // reload by resetting the URL
+
+ //this.properties.status = "loading";
+ //this.properties.title = undefined;
+
+ Queue.enqueue(this, function(done) {
+ // reset the readyState + title
+ this.properties.status = "loading";
+ this.properties.title = undefined;
+
+ chrome.tabs.reload(
+ this.properties.id,
+ { bypassCache: true },
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+// Web Messaging support for BrowserTab objects
+BrowserTab.prototype.postMessage = function( postData ) {
+
+ // Cannot send messages if tab is in the closed state
+ if(this.properties.closed === true) {
+ throw new OError(
+ "InvalidStateError",
+ "The current BrowserTab object is in the closed state and therefore is invalid.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.sendMessage(
+ this.properties.id,
+ postData,
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+// Screenshot API support for BrowserTab objects
+BrowserTab.prototype.getScreenshot = function( callback ) {
+
+ // Cannot get a screenshot if tab is in the closed state
+ if(this.properties.closed === true) {
+ throw new OError(
+ "InvalidStateError",
+ "The current BrowserTab object is in the closed state and therefore is invalid.",
+ DOMException.INVALID_STATE_ERR
+ );
+ }
+
+ if( !this._windowParent || this._windowParent.properties.closed === true) {
+ callback.call( this, undefined );
+ return;
+ }
+
+ try {
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ chrome.tabs.captureVisibleTab(
+ this._windowParent.properties.id,
+ {},
+ function( nativeCallback ) {
+
+ if( nativeCallback ) {
+
+ // Convert the returned dataURL in to an ImageData object and
+ // return via the main callback function argument
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d');
+ var img = new Image();
+ img.onload = function(){
+ canvas.width = img.width;
+ canvas.height = img.height;
+ ctx.drawImage(img,0,0);
+
+ var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ // Return the ImageData object to the callee
+ callback.call( this, imageData );
+
+ }.bind(this);
+ img.src = nativeCallback;
+
+ } else {
+
+ callback.call( this, undefined );
+
+ }
+
+ done();
+
+ }.bind(this)
+ );
+ }.bind(this));
+
+ } catch(e) {}
+
+};
+
+BrowserTab.prototype.close = function() {
+
+ if(this.properties.closed == true) {
+ /*throw new OError(
+ "InvalidStateError",
+ "The current BrowserTab object is already closed. Cannot call 'close' on this object.",
+ DOMException.INVALID_STATE_ERR
+ );*/
+ return;
+ }
+
+ // Cannot close pinned tab
+ if(this.properties.pinned == true) {
+ return;
+ }
+
+ // Set BrowserTab object to closed state
+ this.properties.closed = true;
+
+ this.properties.active = false;
+
+ // Detach from parent window
+ this._oldWindowParent = this._windowParent;
+ //this._windowParent = null;
+
+ // Remove index
+ this._oldIndex = this.properties.index;
+ this.properties.index = undefined;
+
+ // Remove tab from current collection
+ if(this._oldWindowParent) {
+ this._oldWindowParent.tabs.removeTab( this );
+ }
+
+ // Remove tab from global collection
+ OEX.tabs.removeTab( this );
+
+ // Queue platform action or fire immediately if this object is resolved
+ Queue.enqueue(this, function(done) {
+ if(!this.properties.id) return;
+ chrome.tabs.remove(
+ this.properties.id,
+ function() {
+ done();
+ }.bind(this)
+ );
+ }.bind(this));
+
+};
+
+var BrowserTabGroupManager = function( parentObj ) {
+
+ OEventTarget.call(this);
+
+ this._parent = parentObj;
+
+ // Set up 0 mock BrowserTabGroup objects at startup
+ this.length = 0;
+
+};
+
+BrowserTabGroupManager.prototype = Object.create( OEventTarget.prototype );
+
+BrowserTabGroupManager.prototype.create = function() {
+
+ // When this feature is not supported in the current user agent then we must
+ // throw a NOT_SUPPORTED_ERR as per the full Opera WinTabs API specification.
+ throw new OError(
+ "NotSupportedError",
+ "The current user agent does not support the Tab Groups feature.",
+ DOMException.NOT_SUPPORTED_ERR
+ );
+
+};
+
+BrowserTabGroupManager.prototype.getAll = function() {
+ return []; // always empty
+};
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.windows = OEX.windows || new BrowserWindowManager();
+
+} else {
+
+ // Set WinTabs feature to LOADED
+ deferredComponentsLoadStatus['WINTABS_LOADED'] = true;
+
+}
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.tabs = OEX.tabs || new RootBrowserTabManager();
+
+} else {
+
+ // Set WinTabs feature to LOADED
+ deferredComponentsLoadStatus['WINTABS_LOADED'] = true;
+
+}
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.tabGroups = OEX.tabGroups || new BrowserTabGroupManager();
+
+}
+
+var ToolbarContext = function() {
+
+ OEventTarget.call( this );
+
+ this.length = 0;
+
+ // Unfortunately, click events only fire if a popup is not supplied
+ // to a registered browser action in Chromium :(
+ // http://stackoverflow.com/questions/1938356/chrome-browser-action-click-not-working
+ //
+ // TODO also invoke clickEventHandler function when a popup page loads
+ function clickEventHandler(_tab) {
+
+ if( this[ 0 ] ) {
+ this[ 0 ].dispatchEvent( new OEvent('click', {}) );
+ }
+
+ // Fire event also on ToolbarContext API stub
+ this.dispatchEvent( new OEvent('click', {}) );
+
+ }
+
+ chrome.browserAction.onClicked.addListener(clickEventHandler.bind(this));
+
+};
+
+ToolbarContext.prototype = Object.create( OEventTarget.prototype );
+
+ToolbarContext.prototype.createItem = function( toolbarUIItemProperties ) {
+ return new ToolbarUIItem( toolbarUIItemProperties );
+};
+
+ToolbarContext.prototype.addItem = function( toolbarUIItem ) {
+
+ if( !toolbarUIItem || !toolbarUIItem instanceof ToolbarUIItem ) {
+ return;
+ }
+
+ this[ 0 ] = toolbarUIItem;
+ this.length = 1;
+
+ toolbarUIItem.resolve(true);
+ toolbarUIItem.apply();
+
+ toolbarUIItem.badge.resolve(true);
+ toolbarUIItem.badge.apply();
+
+ toolbarUIItem.popup.resolve(true);
+ toolbarUIItem.popup.apply();
+
+};
+
+ToolbarContext.prototype.removeItem = function( toolbarUIItem ) {
+
+ if( !toolbarUIItem || !toolbarUIItem instanceof ToolbarUIItem ) {
+ return;
+ }
+
+ if( this[ 0 ] && this[ 0 ] === toolbarUIItem ) {
+
+ delete this[ 0 ];
+ this.length = 0;
+
+ // Disable the toolbar button
+ chrome.browserAction.disable();
+
+ toolbarUIItem.dispatchEvent( new OEvent('remove', {}) );
+
+ // Fire event on self
+ this.dispatchEvent( new OEvent('remove', {}) );
+
+ }
+
+};
+
+var ToolbarBadge = function( properties ) {
+
+ OPromise.call( this );
+
+ this.properties = {};
+
+ // Set provided properties through object prototype setter functions
+ this.properties.textContent = properties.textContent ? "" + properties.textContent : properties.textContent;
+ this.properties.backgroundColor = complexColorToHex(properties.backgroundColor);
+ this.properties.color = complexColorToHex(properties.color);
+ this.properties.display = String(properties.display).toLowerCase() === 'none' ? 'none' : 'block';
+};
+
+ToolbarBadge.prototype = Object.create( OPromise.prototype );
+
+ToolbarBadge.prototype.apply = function() {
+
+ chrome.browserAction.setBadgeBackgroundColor({ "color": (this.backgroundColor || "#f00") });
+
+ if( this.display === "block" ) {
+ chrome.browserAction.setBadgeText({ "text": this.textContent || "" });
+ } else {
+ chrome.browserAction.setBadgeText({ "text": "" });
+ }
+
+};
+
+// API
+
+ToolbarBadge.prototype.__defineGetter__("textContent", function() {
+ return this.properties.textContent;
+});
+
+ToolbarBadge.prototype.__defineSetter__("textContent", function( val ) {
+ this.properties.textContent = "" + val;
+ if( this.properties.display === "block" ) {
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setBadgeText({ "text": ("" + val) });
+
+ done();
+
+ }.bind(this));
+ }
+});
+
+ToolbarBadge.prototype.__defineGetter__("backgroundColor", function() {
+ return this.properties.backgroundColor;
+});
+
+ToolbarBadge.prototype.__defineSetter__("backgroundColor", function( val ) {
+ this.properties.backgroundColor = complexColorToHex(val);
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setBadgeBackgroundColor({ "color": this.properties.backgroundColor });
+
+ done();
+
+ }.bind(this));
+});
+
+ToolbarBadge.prototype.__defineGetter__("color", function() {
+ return this.properties.color;
+});
+
+ToolbarBadge.prototype.__defineSetter__("color", function( val ) {
+ this.properties.color = complexColorToHex(val);
+ // not implemented in chromium
+});
+
+ToolbarBadge.prototype.__defineGetter__("display", function() {
+ return this.properties.display;
+});
+
+ToolbarBadge.prototype.__defineSetter__("display", function( val ) {
+ if(("" + val).toLowerCase() === "none") {
+ this.properties.display = "none";
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setBadgeText({ "text": "" });
+
+ done();
+ }.bind(this));
+ }
+
+ else {
+ this.properties.display = "block";
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setBadgeText({ "text": this.properties.textContent });
+
+ done();
+
+ }.bind(this));
+ }
+});
+
+var ToolbarPopup = function( properties ) {
+
+ OPromise.call( this );
+
+ this.properties = {
+ href: "",
+ width: 300,
+ height: 300
+ };
+
+ // internal properties
+ this.isExternalHref = false;
+
+ this.href = properties.href;
+ this.width = properties.width;
+ this.height = properties.height;
+
+ this.applyHrefVal = function() {
+ // If href points to a http or https resource we need to load it via an iframe
+ if(this.isExternalHref === true) {
+ return "/oex_shim/popup_resourceloader.html?href=" + global.btoa(this.properties.href) +
+ "&w=" + this.properties.width + "&h=" + this.properties.height;
+ }
+
+ return this.properties.href + (this.properties.href.indexOf('?') > 0 ? '&' : '?' ) +
+ "w=" + this.properties.width + "&h=" + this.properties.height;
+ };
+
+};
+
+ToolbarPopup.prototype = Object.create( OPromise.prototype );
+
+ToolbarPopup.prototype.apply = function() {
+
+ if(this.properties.href && this.properties.href !== "undefined" && this.properties.href !== "null" && this.properties.href !== "") {
+
+ chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });
+
+ } else {
+
+ chrome.browserAction.setPopup({ "popup": "" });
+
+ }
+
+};
+
+// API
+
+ToolbarPopup.prototype.__defineGetter__("href", function() {
+ return this.properties.href;
+});
+
+ToolbarPopup.prototype.__defineSetter__("href", function( val ) {
+ val = val + ""; // force to type string
+
+ // Check if we have an external href path
+ if(val.match(/^(https?:\/\/|data:)/)) {
+ this.isExternalHref = true;
+ } else {
+ this.isExternalHref = false;
+ }
+
+ this.properties.href = val;
+
+ if(this.properties.href && this.properties.href !== "undefined" && this.properties.href !== "null" && this.properties.href !== "") {
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });
+
+ done();
+
+ }.bind(this));
+
+ } else {
+
+ chrome.browserAction.setPopup({ "popup": "" });
+
+ }
+});
+
+ToolbarPopup.prototype.__defineGetter__("width", function() {
+ return this.properties.width;
+});
+
+ToolbarPopup.prototype.__defineSetter__("width", function( val ) {
+ val = (val + "").replace(/\D/g, '');
+
+ if(val == '') {
+ this.properties.width = 300; // default width
+ } else {
+ this.properties.width = val < 800 ? val : 800; // enforce max width
+ }
+
+ if(this.properties.href && this.properties.href !== "undefined" && this.properties.href !== "null" && this.properties.href !== "") {
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });
+
+ done();
+
+ }.bind(this));
+
+ } else {
+
+ chrome.browserAction.setPopup({ "popup": "" });
+
+ }
+});
+
+ToolbarPopup.prototype.__defineGetter__("height", function() {
+ return this.properties.height;
+});
+
+ToolbarPopup.prototype.__defineSetter__("height", function( val ) {
+ val = (val + "").replace(/\D/g, '');
+
+ if(val == '') {
+ this.properties.height = 300; // default height
+ } else {
+ this.properties.height = val < 600 ? val : 600; // enforce max height
+ }
+
+ if(this.properties.href && this.properties.href !== "undefined" && this.properties.href !== "null" && this.properties.href !== "") {
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });
+
+ done();
+
+ }.bind(this));
+
+ } else {
+
+ chrome.browserAction.setPopup({ "popup": "" });
+
+ }
+});
+
+var ToolbarUIItem = function( properties ) {
+
+ OPromise.call( this );
+
+ this.properties = {};
+
+ this.properties.disabled = properties.disabled || false;
+ this.properties.title = properties.title || "";
+ this.properties.icon = properties.icon || "";
+ this.properties.popup = new ToolbarPopup( properties.popup || {} );
+ this.properties.badge = new ToolbarBadge( properties.badge || {} );
+ if(properties.onclick){this.onclick = properties.onclick;}
+ if(properties.onremove){this.onremove = properties.onremove;}
+
+};
+
+ToolbarUIItem.prototype = Object.create( OPromise.prototype );
+
+ToolbarUIItem.prototype.apply = function() {
+
+ // Apply title property
+ chrome.browserAction.setTitle({ "title": this.title });
+
+ // Apply icon property
+ if(this.icon) {
+ chrome.browserAction.setIcon({ "path": this.icon });
+ }
+
+ // Apply disabled property
+ if( this.disabled === true ) {
+ chrome.browserAction.disable();
+ } else {
+ chrome.browserAction.enable();
+ }
+
+};
+
+// API
+
+ToolbarUIItem.prototype.__defineGetter__("disabled", function() {
+ return this.properties.disabled;
+});
+
+ToolbarUIItem.prototype.__defineSetter__("disabled", function( val ) {
+ if( this.properties.disabled !== val ) {
+ if( val === true || val === "true" || val === 1 || val === "1" ) {
+ this.properties.disabled = true;
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.disable();
+
+ done();
+
+ }.bind(this));
+ } else {
+ this.properties.disabled = false;
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.enable();
+
+ done();
+
+ }.bind(this));
+ }
+ }
+});
+
+ToolbarUIItem.prototype.__defineGetter__("title", function() {
+ return this.properties.title;
+});
+
+ToolbarUIItem.prototype.__defineSetter__("title", function( val ) {
+ this.properties.title = "" + val;
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setTitle({ "title": this.title });
+
+ done();
+
+ }.bind(this));
+});
+
+ToolbarUIItem.prototype.__defineGetter__("icon", function() {
+ return this.properties.icon;
+});
+
+ToolbarUIItem.prototype.__defineSetter__("icon", function( val ) {
+ this.properties.icon = "" + val;
+
+ Queue.enqueue(this, function(done) {
+
+ chrome.browserAction.setIcon({ "path": this.icon });
+
+ done();
+
+ }.bind(this));
+});
+
+ToolbarUIItem.prototype.__defineGetter__("popup", function() {
+ return this.properties.popup;
+});
+
+ToolbarUIItem.prototype.__defineGetter__("badge", function() {
+ return this.properties.badge;
+});
+
+if(manifest && manifest.browser_action !== undefined && manifest.browser_action !== null ) {
+
+ OEC.toolbar = OEC.toolbar || new ToolbarContext();
+
+}
+var MenuEvent = function(type,args,target){
+ var event;
+
+ if(type=='click'){
+
+ var tab = null;
+ var tabs = OEX.tabs.getAll();
+ for(var i=0;i0){
+ throw new OError(
+ "NotSupportedError",
+ "NOT_SUPPORTED_ERR",
+ DOMException.NOT_SUPPORTED_ERR
+ );
+ return;
+ };
+
+ //no item to add
+ if( !menuItem || !(menuItem instanceof MenuItem) ) {
+ throw new OError(
+ "TypeMismatchError",
+ "TYPE_MISMATCH_ERR",
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ return;
+ }
+
+ //adding only for folders
+ if(this instanceof MenuItem && this.type!='folder'){
+ throw new OError(
+ "TypeMismatchError",
+ "TYPE_MISMATCH_ERR",
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ return;
+ };
+
+ if(Array.prototype.indexOf.apply(this,[menuItem])!=-1)return;//already exist
+
+ //same parent check
+ if(before===undefined || this instanceof MenuContext)before = this.length;
+ else if(before instanceof MenuItem){
+ var index = Array.prototype.indexOf.apply(this,[before]);
+ if(before.parent != this || index == -1){
+ throw new OError(
+ "HierarchyRequestError",
+ "HIERARCHY_REQUEST_ERR",
+ DOMException.HIERARCHY_REQUEST_ERR
+ );
+ return;
+
+ } else before = index;
+
+ } else if(before === null)before = this.length;
+ else before = toUint32(before);
+
+ if(isNaN(before))before = 0;
+
+ //loop check
+ var parent = this;
+ var noLoop = false;
+ while(!noLoop){
+ if(parent instanceof MenuContext || parent == null)noLoop = true;
+ else if(parent === menuItem){
+ throw new OError(
+ "HierarchyRequestError",
+ "HIERARCHY_REQUEST_ERR",
+ DOMException.HIERARCHY_REQUEST_ERR
+ );
+ return;
+ } else parent = parent.parent;
+
+ };
+
+
+ Array.prototype.splice.apply(this,[before,0,menuItem]);
+
+ length = length + 1;
+
+ if(this instanceof MenuContext)menuItem.dispatchEvent( new MenuEvent('change', {properties : {parent: this} },menuItem));
+ else this.dispatchEvent( new MenuEvent('change', {properties : {parent: this} }, menuItem));
+
+ }});
+
+ Object.defineProperty(this,'removeItem',{enumerable: true, configurable: false, writable: false, value: function(index){
+ if(index===undefined) {
+ throw new OError(
+ "TypeMismatchError",
+ "TYPE_MISMATCH_ERR",
+ DOMException.TYPE_MISMATCH_ERR
+ );
+ return;
+ };
+
+ if(index<0 || index >= length || this[ index ] == undefined)return;
+
+ this[ index ].dispatchEvent( new MenuEvent('change', {properties : {parent: null} },this[ index ]));
+
+ Array.prototype.splice.apply(this,[index,1]);
+
+ length = length - 1;
+
+ }});
+
+ Object.defineProperty(this,'item',{enumerable: true, configurable: false, writable: false, value: function(index){
+ return this[index] || null;
+ }});
+
+};
+
+OMenuContext.prototype = Object.create( MenuEventTarget.prototype);
+
+var MenuItemProperties = function(obj,initial){
+ var lock = false;
+ var menuItemId = null;
+ var properties = {
+ id: "",
+ type: "entry",
+ contexts: ["page"],
+ disabled: false,
+ title: "",
+ icon: "",
+ documentURLPatterns: null,
+ targetURLPatterns: null,
+ parent: null
+ };
+ var allowedContexts = ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"];
+ var changed = function(){
+ if(!lock)obj.dispatchEvent(new MenuEvent('change',{},obj));
+ }
+ var update = function(props){
+ if(lock)return;
+
+ lock = true;
+
+ if(props!=undefined)for(var name in props)if(properties[name]!==undefined){
+ if(name === "type"){
+
+ if(["entry", "folder", "line"].indexOf(String(props.type).toLowerCase())!=-1)properties.type = String(props.type);
+
+ } else if(name === "parent"){
+ if(props.parent === null || props.parent instanceof OMenuContext)properties.parent = props.parent;
+ else throw new TypeError();
+
+ } else if(name === "id")properties.id = String(props.id);
+ else obj[name] = props[name];
+ };
+
+ lock = false;
+ //update
+
+ if(properties.parent==null || (properties.parent instanceof MenuItem && properties.parent.menuItemId==null) ){
+
+ if(menuItemId!=null){
+ chrome.contextMenus.remove(menuItemId);
+ menuItemId = null;
+ };
+
+ } else if(properties.disabled==true){
+
+ if(menuItemId!=null){
+ chrome.contextMenus.remove(menuItemId);
+ menuItemId = null;
+ };
+
+ } else {
+
+ var updateProperties = {
+ title: properties.title.length==0?chrome.app.getDetails().name:properties.title,
+ type: properties.type.toLowerCase()=="line"?"separator":"normal" //"normal", "checkbox", "radio", "separator"
+ };
+
+ var contexts = properties.contexts.join(',').toLowerCase().split(',').filter(function(element){
+ return allowedContexts.indexOf(element.toLowerCase())!=-1;
+ });
+
+ if(contexts.length==0)updateProperties.contexts = ["page"];
+ else updateProperties.contexts = contexts;
+
+ if(properties.parent instanceof MenuItem && properties.parent.menuItemId!=null){
+ updateProperties.parentId = properties.parent.menuItemId;
+ };
+
+
+
+ if(menuItemId==null){
+ if(properties.id != "")updateProperties.id = properties.id;//set id
+ menuItemId = chrome.contextMenus.create(updateProperties);
+ } else chrome.contextMenus.update(menuItemId,updateProperties);
+
+ /* unsafe code
+ if(
+ this.properties.parent instanceof MenuContext && this.properties.icon.length>0 //has icon
+ && !(chrome.app.getDetails().icons && chrome.app.getDetails().icons[16]) // no global 16x16 icon
+ ){//set custom root icon
+ chrome.browserAction.setIcon({path: this.properties.icon });
+ };
+ */
+
+ };
+
+ };
+
+ var nosetter = function(value){};
+
+ Object.defineProperty(obj,'id',{enumerable: true, configurable: false, get: function(){return properties.id;}, set: nosetter});
+ Object.defineProperty(obj,'type',{enumerable: true, configurable: false, get: function(){return properties.type;}, set: nosetter});
+
+ Object.defineProperty(obj,'contexts',{enumerable: true, configurable: false, get: function(){return properties.contexts;}, set: function(value){
+ if(!Array.isArray(value)){
+ throw new TypeError();
+ return;
+ };
+
+ properties.contexts = value.length==0?value:value.join(',').split(',');
+ changed();
+
+ }});
+
+ Object.defineProperty(obj,'disabled',{enumerable: true, configurable: false, get: function(){return properties.disabled;}, set: function(value){
+ properties.disabled = Boolean(value);
+ changed();
+ }});
+
+ Object.defineProperty(obj,'title',{enumerable: true, configurable: false, get: function(){return properties.title;}, set: function(value){
+ properties.title = String(value);
+ changed();
+ }});
+
+ Object.defineProperty(obj,'icon',{enumerable: true, configurable: false, get: function(){return properties.icon;}, set: function(value){
+ if(typeof value === "string"){
+ properties.icon = value;
+
+ if(properties.icon.indexOf(':')==-1&&properties.icon.indexOf('/')==-1&&properties.icon.length>0)properties.icon = '/'+properties.icon;
+ };
+
+ changed();
+ }});
+
+ Object.defineProperty(obj,'documentURLPatterns',{enumerable: true, configurable: false, get: function(){return properties.documentURLPatterns;}, set: function(value){
+
+ if(Array.isArray(value)){
+ properties.documentURLPatterns = [];
+ for(var i=0;i .
+ */
+
+//
+// Module framework stuff
+//
+
+function require(module)
+{
+ return require.scopes[module];
+}
+require.scopes = {__proto__: null};
+
+function importAll(module, globalObj)
+{
+ var exports = require(module);
+ for (var key in exports)
+ globalObj[key] = exports[key];
+}
+
+onShutdown = {
+ done: false,
+ add: function() {},
+ remove: function() {}
+};
+
+//
+// XPCOM emulation
+//
+
+var Components =
+{
+ interfaces:
+ {
+ nsIFile: {DIRECTORY_TYPE: 0},
+ nsIFileURL: function() {},
+ nsIHttpChannel: function() {},
+ nsITimer: {TYPE_REPEATING_SLACK: 0},
+ nsIInterfaceRequestor: null,
+ nsIChannelEventSink: null
+ },
+ classes:
+ {
+ "@mozilla.org/timer;1":
+ {
+ createInstance: function()
+ {
+ return new FakeTimer();
+ }
+ },
+ "@mozilla.org/xmlextras/xmlhttprequest;1":
+ {
+ createInstance: function()
+ {
+ return new XMLHttpRequest();
+ }
+ }
+ },
+ results: {},
+ utils: {
+ reportError: function(e)
+ {
+ console.error(e);
+ console.trace();
+ }
+ },
+ manager: null,
+ ID: function()
+ {
+ return null;
+ }
+};
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+var XPCOMUtils =
+{
+ generateQI: function() {}
+};
+
+//
+// Info pseudo-module
+//
+
+require.scopes.info =
+{
+ get addonID()
+ {
+ return chrome.i18n.getMessage("@@extension_id");
+ },
+ addonVersion: "2.1", // Hardcoded for now
+ addonRoot: "",
+ get addonName()
+ {
+ return chrome.i18n.getMessage("name");
+ },
+ application: "chrome"
+};
+
+//
+// IO module: no direct file system access, using FileSystem API
+//
+
+require.scopes.io =
+{
+ IO: {
+ _getFileEntry: function(file, create, successCallback, errorCallback)
+ {
+ if (file instanceof FakeFile)
+ file = file.path;
+ else if ("spec" in file)
+ file = file.spec;
+
+ // Remove directory path - we operate on a single directory in Chrome
+ file = file.replace(/^.*[\/\\]/, "");
+
+ // We request a gigabyte of space, just in case
+ (window.requestFileSystem || window.webkitRequestFileSystem)(window.PERSISTENT, 1024*1024*1024, function(fs)
+ {
+ fs.root.getFile(file, {create: create}, function(fileEntry)
+ {
+ successCallback(fs, fileEntry);
+ }, errorCallback);
+ }, errorCallback);
+ },
+
+ lineBreak: "\n",
+
+ resolveFilePath: function(path)
+ {
+ return new FakeFile(path);
+ },
+
+ readFromFile: function(file, decode, listener, callback, timeLineID)
+ {
+ if ("spec" in file && /^defaults\b/.test(file.spec))
+ {
+ // Code attempts to read the default patterns.ini, we don't have that.
+ // Make sure to execute first-run actions instead.
+ callback(null);
+ if (localStorage.currentVersion)
+ seenDataCorruption = true;
+ delete localStorage.currentVersion;
+ return;
+ }
+
+ this._getFileEntry(file, false, function(fs, fileEntry)
+ {
+ fileEntry.file(function(file)
+ {
+ var reader = new FileReader();
+ reader.onloadend = function()
+ {
+ if (reader.error)
+ callback(reader.error);
+ else
+ {
+ var lines = reader.result.split(/[\r\n]+/);
+ for (var i = 0; i < lines.length; i++)
+ listener.process(lines[i]);
+ listener.process(null);
+ callback(null);
+ }
+ };
+ reader.readAsText(file);
+ }, callback);
+ }, callback);
+ },
+
+ writeToFile: function(file, encode, data, callback, timeLineID)
+ {
+ this._getFileEntry(file, true, function(fs, fileEntry)
+ {
+ fileEntry.createWriter(function(writer)
+ {
+ var executeWriteOperation = function(op, nextOperation)
+ {
+ writer.onwriteend = function()
+ {
+ if (writer.error)
+ callback(writer.error);
+ else
+ nextOperation();
+ }.bind(this);
+
+ op();
+ }.bind(this);
+
+ executeWriteOperation(writer.truncate.bind(writer, 0), function()
+ {
+ var blob;
+ try
+ {
+ blob = new Blob([data.join(this.lineBreak) + this.lineBreak], {type: "text/plain"});
+ }
+ catch (e)
+ {
+ if (!(e instanceof TypeError))
+ throw e;
+
+ // Blob wasn't a constructor before Chrome 20
+ var builder = new (window.BlobBuilder || window.WebKitBlobBuilder);
+ builder.append(data.join(this.lineBreak) + this.lineBreak);
+ blob = builder.getBlob("text/plain");
+ }
+ executeWriteOperation(writer.write.bind(writer, blob), callback.bind(null, null));
+ }.bind(this));
+ }.bind(this), callback);
+ }.bind(this), callback);
+ },
+
+ copyFile: function(fromFile, toFile, callback)
+ {
+ // Simply combine read and write operations
+ var data = [];
+ this.readFromFile(fromFile, false, {
+ process: function(line)
+ {
+ if (line !== null)
+ data.push(line);
+ }
+ }, function(e)
+ {
+ if (e)
+ callback(e);
+ else
+ this.writeToFile(toFile, false, data, callback);
+ }.bind(this));
+ },
+
+ renameFile: function(fromFile, newName, callback)
+ {
+ this._getFileEntry(fromFile, false, function(fs, fileEntry)
+ {
+ fileEntry.moveTo(fs.root, newName, function()
+ {
+ callback(null);
+ }, callback);
+ }, callback);
+ },
+
+ removeFile: function(file, callback)
+ {
+ this._getFileEntry(file, false, function(fs, fileEntry)
+ {
+ fileEntry.remove(function()
+ {
+ callback(null);
+ }, callback);
+ }, callback);
+ },
+
+ statFile: function(file, callback)
+ {
+ this._getFileEntry(file, false, function(fs, fileEntry)
+ {
+ fileEntry.getMetadata(function(metadata)
+ {
+ callback(null, {
+ exists: true,
+ isDirectory: fileEntry.isDirectory,
+ isFile: fileEntry.isFile,
+ lastModified: metadata.modificationTime.getTime()
+ });
+ }, callback);
+ }, callback);
+ }
+ }
+};
+
+//
+// Fake nsIFile implementation for our I/O
+//
+
+function FakeFile(path)
+{
+ this.path = path;
+}
+FakeFile.prototype =
+{
+ get leafName()
+ {
+ return this.path;
+ },
+ set leafName(value)
+ {
+ this.path = value;
+ },
+ append: function(path)
+ {
+ this.path += path;
+ },
+ clone: function()
+ {
+ return new FakeFile(this.path);
+ },
+ get parent()
+ {
+ return {create: function() {}};
+ },
+ normalize: function() {}
+};
+
+//
+// Prefs module: the values are hardcoded for now.
+//
+
+require.scopes.prefs = {
+ Prefs: {
+ enabled: true,
+ patternsfile: "patterns.ini",
+ patternsbackups: 5,
+ patternsbackupinterval: 24,
+ data_directory: "",
+ savestats: false,
+ privateBrowsing: false,
+ subscriptions_fallbackerrors: 5,
+ subscriptions_fallbackurl: "https://adblockplus.org/getSubscription?version=%VERSION%&url=%SUBSCRIPTION%&downloadURL=%URL%&error=%ERROR%&channelStatus=%CHANNELSTATUS%&responseStatus=%RESPONSESTATUS%",
+ subscriptions_autoupdate: true,
+ subscriptions_exceptionsurl: "https://easylist-downloads.adblockplus.org/exceptionrules.txt",
+ documentation_link: "https://adblockplus.org/redirect?link=%LINK%&lang=%LANG%",
+ addListener: function() {}
+ }
+};
+
+//
+// Utils module
+//
+
+require.scopes.utils =
+{
+ Utils: {
+ systemPrincipal: null,
+ getString: function(id)
+ {
+ return id;
+ },
+ runAsync: function(callback, thisPtr)
+ {
+ var params = Array.prototype.slice.call(arguments, 2);
+ window.setTimeout(function()
+ {
+ callback.apply(thisPtr, params);
+ }, 0);
+ },
+ get appLocale()
+ {
+ var locale = chrome.i18n.getMessage("@@ui_locale").replace(/_/g, "-");
+ this.__defineGetter__("appLocale", function() {return locale});
+ return this.appLocale;
+ },
+ generateChecksum: function(lines)
+ {
+ // We cannot calculate MD5 checksums yet :-(
+ return null;
+ },
+ makeURI: function(url)
+ {
+ return Services.io.newURI(url);
+ },
+
+ checkLocalePrefixMatch: function(prefixes)
+ {
+ if (!prefixes)
+ return null;
+
+ var list = prefixes.split(",");
+ for (var i = 0; i < list.length; i++)
+ if (new RegExp("^" + list[i] + "\\b").test(this.appLocale))
+ return list[i];
+
+ return null;
+ },
+
+ chooseFilterSubscription: function(subscriptions)
+ {
+ var selectedItem = null;
+ var selectedPrefix = null;
+ var matchCount = 0;
+ for (var i = 0; i < subscriptions.length; i++)
+ {
+ var subscription = subscriptions[i];
+ if (!selectedItem)
+ selectedItem = subscription;
+
+ var prefix = require("utils").Utils.checkLocalePrefixMatch(subscription.getAttribute("prefixes"));
+ if (prefix)
+ {
+ if (!selectedPrefix || selectedPrefix.length < prefix.length)
+ {
+ selectedItem = subscription;
+ selectedPrefix = prefix;
+ matchCount = 1;
+ }
+ else if (selectedPrefix && selectedPrefix.length == prefix.length)
+ {
+ matchCount++;
+
+ // If multiple items have a matching prefix of the same length:
+ // Select one of the items randomly, probability should be the same
+ // for all items. So we replace the previous match here with
+ // probability 1/N (N being the number of matches).
+ if (Math.random() * matchCount < 1)
+ {
+ selectedItem = subscription;
+ selectedPrefix = prefix;
+ }
+ }
+ }
+ }
+ return selectedItem;
+ }
+ }
+};
+
+//
+// ElemHideHitRegistration dummy implementation
+//
+
+require.scopes.elemHideHitRegistration =
+{
+ AboutHandler: {}
+};
+
+//
+// Services.jsm module emulation
+//
+
+var Services =
+{
+ io: {
+ newURI: function(uri)
+ {
+ if (!uri.length || uri[0] == "~")
+ throw new Error("Invalid URI");
+
+ /^([^:\/]*)/.test(uri);
+ var scheme = RegExp.$1.toLowerCase();
+
+ return {
+ scheme: scheme,
+ spec: uri,
+ QueryInterface: function()
+ {
+ return this;
+ }
+ };
+ },
+ newFileURI: function(file)
+ {
+ var result = this.newURI("file:///" + file.path);
+ result.file = file;
+ return result;
+ }
+ },
+ obs: {
+ addObserver: function() {},
+ removeObserver: function() {}
+ },
+ vc: {
+ compare: function(v1, v2)
+ {
+ function parsePart(s)
+ {
+ if (!s)
+ return parsePart("0");
+
+ var part = {
+ numA: 0,
+ strB: "",
+ numC: 0,
+ extraD: ""
+ };
+
+ if (s === "*")
+ {
+ part.numA = Number.MAX_VALUE;
+ return part;
+ }
+
+ var matches = s.match(/(\d*)(\D*)(\d*)(.*)/);
+ part.numA = parseInt(matches[1], 10) || part.numA;
+ part.strB = matches[2] || part.strB;
+ part.numC = parseInt(matches[3], 10) || part.numC;
+ part.extraD = matches[4] || part.extraD;
+
+ if (part.strB == "+")
+ {
+ part.numA++;
+ part.strB = "pre";
+ }
+
+ return part;
+ }
+
+ function comparePartElement(s1, s2)
+ {
+ if (s1 === "" && s2 !== "")
+ return 1;
+ if (s1 !== "" && s2 === "")
+ return -1;
+ return s1 === s2 ? 0 : (s1 > s2 ? 1 : -1);
+ }
+
+ function compareParts(p1, p2)
+ {
+ var result = 0;
+ var elements = ["numA", "strB", "numC", "extraD"];
+ elements.some(function(element)
+ {
+ result = comparePartElement(p1[element], p2[element]);
+ return result;
+ });
+ return result;
+ }
+
+ var parts1 = v1.split(".");
+ var parts2 = v2.split(".");
+ for (var i = 0; i < Math.max(parts1.length, parts2.length); i++)
+ {
+ var result = compareParts(parsePart(parts1[i]), parsePart(parts2[i]));
+ if (result)
+ return result;
+ }
+ return 0;
+ }
+ }
+}
+
+//
+// FileUtils.jsm module emulation
+//
+
+var FileUtils =
+{
+ PERMS_DIRECTORY: 0
+};
+
+function FakeTimer()
+{
+}
+FakeTimer.prototype =
+{
+ delay: 0,
+ callback: null,
+ initWithCallback: function(callback, delay)
+ {
+ this.callback = callback;
+ this.delay = delay;
+ this.scheduleTimeout();
+ },
+ scheduleTimeout: function()
+ {
+ var me = this;
+ window.setTimeout(function()
+ {
+ try
+ {
+ me.callback();
+ }
+ catch(e)
+ {
+ Cu.reportError(e);
+ }
+ me.scheduleTimeout();
+ }, this.delay);
+ }
+};
+
+//
+// Add a channel property to XMLHttpRequest, Synchronizer needs it
+//
+
+XMLHttpRequest.prototype.channel =
+{
+ status: -1,
+ notificationCallbacks: {},
+ loadFlags: 0,
+ INHIBIT_CACHING: 0,
+ VALIDATE_ALWAYS: 0,
+ QueryInterface: function()
+ {
+ return this;
+ }
+};
+
+/*
+ * This file is part of the Adblock Plus extension,
+ * Copyright (C) 2006-2012 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+//
+// This file has been generated automatically from Adblock Plus for Firefox
+// source code. DO NOT MODIFY, change the original source code instead.
+//
+// Relevant repositories:
+// * https://hg.adblockplus.org/adblockplus/
+// * https://hg.adblockplus.org/jshydra/
+//
+
+/**
+ * Minor modifications for compatibility with operaextensions.js
+ *
+ * You can view changes by diffing this file with:
+ * https://github.com/adblockplus/adblockpluschrome/blob/0f158ee8c97390b831bac12016241613b4276729/lib/adblockplus.js
+ *
+ */
+
+require.scopes["filterNotifier"] = (function()
+{
+ var exports = {};
+ var listeners = [];
+ var FilterNotifier = exports.FilterNotifier =
+ {
+ addListener: function(listener)
+ {
+ if (listeners.indexOf(listener) >= 0)
+ {
+ return;
+ }
+ listeners.push(listener);
+ },
+ removeListener: function(listener)
+ {
+ var index = listeners.indexOf(listener);
+ if (index >= 0)
+ {
+ listeners.splice(index, 1);
+ }
+ },
+ triggerListeners: function(action, item, param1, param2, param3)
+ {
+ for (var _loopIndex0 = 0; _loopIndex0 < listeners.length; ++_loopIndex0)
+ {
+ var listener = listeners[_loopIndex0];
+ listener(action, item, param1, param2, param3);
+ }
+ }
+ };
+ return exports;
+})();
+require.scopes["filterClasses"] = (function()
+{
+ var exports = {};
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+
+ function Filter(text)
+ {
+ this.text = text;
+ this.subscriptions = [];
+ }
+ exports.Filter = Filter;
+ Filter.prototype =
+ {
+ text: null,
+ subscriptions: null,
+ serialize: function(buffer)
+ {
+ buffer.push("[Filter]");
+ buffer.push("text=" + this.text);
+ },
+ toString: function()
+ {
+ return this.text;
+ }
+ };
+ Filter.knownFilters =
+ {
+ __proto__: null
+ };
+ Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(\@)?(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
+ Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/;
+ Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/;
+ Filter.fromText = function(text)
+ {
+ if (text in Filter.knownFilters)
+ {
+ return Filter.knownFilters[text];
+ }
+ var ret;
+ // element hiding is not supported in Opera's URL Filter API
+ // TODO: remove all elemhide related code
+ var match = null;
+ if (match)
+ {
+ ret = ElemHideBase.fromText(text, match[1], match[2], match[3], match[4], match[5]);
+ }
+ else if (text[0] == "!")
+ {
+ ret = new CommentFilter(text);
+ }
+ else
+ {
+ ret = RegExpFilter.fromText(text);
+ }
+ Filter.knownFilters[ret.text] = ret;
+ return ret;
+ };
+ Filter.fromObject = function(obj)
+ {
+ var ret = Filter.fromText(obj.text);
+ if (ret instanceof ActiveFilter)
+ {
+ if ("disabled" in obj)
+ {
+ ret._disabled = obj.disabled == "true";
+ }
+ if ("hitCount" in obj)
+ {
+ ret._hitCount = parseInt(obj.hitCount) || 0;
+ }
+ if ("lastHit" in obj)
+ {
+ ret._lastHit = parseInt(obj.lastHit) || 0;
+ }
+ }
+ return ret;
+ };
+ Filter.normalize = function(text)
+ {
+ if (!text)
+ {
+ return text;
+ }
+ text = text.replace(/[^\S ]/g, "");
+ if (/^\s*!/.test(text))
+ {
+ return text.replace(/^\s+/, "").replace(/\s+$/, "");
+ }
+ else if (Filter.elemhideRegExp.test(text))
+ {
+ var _tempVar1 = /^(.*?)(#\@?#?)(.*)$/.exec(text);
+ var domain = _tempVar1[1];
+ var separator = _tempVar1[2];
+ var selector = _tempVar1[3];
+ return domain.replace(/\s/g, "") + separator + selector.replace(/^\s+/, "").replace(/\s+$/, "");
+ }
+ else
+ {
+ return text.replace(/\s/g, "");
+ }
+ };
+
+ function InvalidFilter(text, reason)
+ {
+ Filter.call(this, text);
+ this.reason = reason;
+ }
+ exports.InvalidFilter = InvalidFilter;
+ InvalidFilter.prototype =
+ {
+ __proto__: Filter.prototype,
+ reason: null,
+ serialize: function(buffer){}
+ };
+
+ function CommentFilter(text)
+ {
+ Filter.call(this, text);
+ }
+ exports.CommentFilter = CommentFilter;
+ CommentFilter.prototype =
+ {
+ __proto__: Filter.prototype,
+ serialize: function(buffer){}
+ };
+
+ function ActiveFilter(text, domains)
+ {
+ Filter.call(this, text);
+ this.domainSource = domains;
+ }
+ exports.ActiveFilter = ActiveFilter;
+ ActiveFilter.prototype =
+ {
+ __proto__: Filter.prototype,
+ _disabled: false,
+ _hitCount: 0,
+ _lastHit: 0,
+ get disabled()
+ {
+ return this._disabled;
+ },
+ set disabled(value)
+ {
+ if (value != this._disabled)
+ {
+ var oldValue = this._disabled;
+ this._disabled = value;
+ FilterNotifier.triggerListeners("filter.disabled", this, value, oldValue);
+ }
+ return this._disabled;
+ },
+ get hitCount()
+ {
+ return this._hitCount;
+ },
+ set hitCount(value)
+ {
+ if (value != this._hitCount)
+ {
+ var oldValue = this._hitCount;
+ this._hitCount = value;
+ FilterNotifier.triggerListeners("filter.hitCount", this, value, oldValue);
+ }
+ return this._hitCount;
+ },
+ get lastHit()
+ {
+ return this._lastHit;
+ },
+ set lastHit(value)
+ {
+ if (value != this._lastHit)
+ {
+ var oldValue = this._lastHit;
+ this._lastHit = value;
+ FilterNotifier.triggerListeners("filter.lastHit", this, value, oldValue);
+ }
+ return this._lastHit;
+ },
+ domainSource: null,
+ domainSeparator: null,
+ ignoreTrailingDot: true,
+ get domains()
+ {
+ var domains = null;
+ if (this.domainSource)
+ {
+ var list = this.domainSource.split(this.domainSeparator);
+ if (list.length == 1 && list[0][0] != "~")
+ {
+ domains =
+ {
+ __proto__: null,
+ "": false
+ };
+ if (this.ignoreTrailingDot)
+ {
+ list[0] = list[0].replace(/\.+$/, "");
+ }
+ domains[list[0]] = true;
+ }
+ else
+ {
+ var hasIncludes = false;
+ for (var i = 0; i < list.length; i++)
+ {
+ var domain = list[i];
+ if (this.ignoreTrailingDot)
+ {
+ domain = domain.replace(/\.+$/, "");
+ }
+ if (domain == "")
+ {
+ continue;
+ }
+ var include;
+ if (domain[0] == "~")
+ {
+ include = false;
+ domain = domain.substr(1);
+ }
+ else
+ {
+ include = true;
+ hasIncludes = true;
+ }
+ if (!domains)
+ {
+ domains =
+ {
+ __proto__: null
+ };
+ }
+ domains[domain] = include;
+ }
+ domains[""] = !hasIncludes;
+ }
+ delete this.domainSource;
+ }
+ this.__defineGetter__("domains", function()
+ {
+ return domains;
+ });
+ return this.domains;
+ },
+ isActiveOnDomain: function(docDomain)
+ {
+ if (!this.domains)
+ {
+ return true;
+ }
+ if (!docDomain)
+ {
+ return this.domains[""];
+ }
+ if (this.ignoreTrailingDot)
+ {
+ docDomain = docDomain.replace(/\.+$/, "");
+ }
+ docDomain = docDomain.toUpperCase();
+ while (true)
+ {
+ if (docDomain in this.domains)
+ {
+ return this.domains[docDomain];
+ }
+ var nextDot = docDomain.indexOf(".");
+ if (nextDot < 0)
+ {
+ break;
+ }
+ docDomain = docDomain.substr(nextDot + 1);
+ }
+ return this.domains[""];
+ },
+ isActiveOnlyOnDomain: function(docDomain)
+ {
+ if (!docDomain || !this.domains || this.domains[""])
+ {
+ return false;
+ }
+ if (this.ignoreTrailingDot)
+ {
+ docDomain = docDomain.replace(/\.+$/, "");
+ }
+ docDomain = docDomain.toUpperCase();
+ for (var domain in this.domains)
+ {
+ if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1))
+ {
+ return false;
+ }
+ }
+ return true;
+ },
+ serialize: function(buffer)
+ {
+ if (this._disabled || this._hitCount || this._lastHit)
+ {
+ Filter.prototype.serialize.call(this, buffer);
+ if (this._disabled)
+ {
+ buffer.push("disabled=true");
+ }
+ if (this._hitCount)
+ {
+ buffer.push("hitCount=" + this._hitCount);
+ }
+ if (this._lastHit)
+ {
+ buffer.push("lastHit=" + this._lastHit);
+ }
+ }
+ }
+ };
+
+ function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty)
+ {
+ ActiveFilter.call(this, text, domains);
+ if (contentType != null)
+ {
+ this.contentType = contentType;
+ }
+ if (matchCase)
+ {
+ this.matchCase = matchCase;
+ }
+ if (thirdParty != null)
+ {
+ this.thirdParty = thirdParty;
+ }
+ if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpSource.length - 1] == "/")
+ {
+ var regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
+ this.__defineGetter__("regexp", function()
+ {
+ return regexp;
+ });
+ }
+ else
+ {
+ this.regexpSource = regexpSource;
+ }
+ }
+ exports.RegExpFilter = RegExpFilter;
+ RegExpFilter.prototype =
+ {
+ __proto__: ActiveFilter.prototype,
+ length: 1,
+ domainSeparator: "|",
+ regexpSource: null,
+ get regexp()
+ {
+ var source = this.regexpSource.replace(/\*+/g, "*");
+ if (source[0] == "*")
+ {
+ source = source.substr(1);
+ }
+ var pos = source.length - 1;
+ if (pos >= 0 && source[pos] == "*")
+ {
+ source = source.substr(0, pos);
+ }
+ source = source.replace(/\^\|$/, "^").replace(/\W/g, "\\$&").replace(/\\\*/g, ".*").replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x80]|$)").replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^.\\/]+\\.)*?").replace(/^\\\|/, "^").replace(/\\\|$/, "$");
+ var regexp = new RegExp(source, this.matchCase ? "" : "i");
+ delete this.regexpSource;
+ this.__defineGetter__("regexp", function()
+ {
+ return regexp;
+ });
+ return this.regexp;
+ },
+ contentType: 2147483647,
+ matchCase: false,
+ thirdParty: null,
+ matches: function(location, contentType, docDomain, thirdParty)
+ {
+ if (this.regexp.test(location) && (RegExpFilter.typeMap[contentType] & this.contentType) != 0 && (this.thirdParty == null || this.thirdParty == thirdParty) && this.isActiveOnDomain(docDomain))
+ {
+ return true;
+ }
+ return false;
+ }
+ };
+ RegExpFilter.prototype.__defineGetter__("0", function()
+ {
+ return this;
+ });
+ RegExpFilter.fromText = function(text)
+ {
+ var blocking = true;
+ var origText = text;
+ if (text.indexOf("@@") == 0)
+ {
+ blocking = false;
+ text = text.substr(2);
+ }
+ var contentType = null;
+ var matchCase = null;
+ var domains = null;
+ var siteKeys = null;
+ var thirdParty = null;
+ var collapse = null;
+ var options;
+ var match = text.indexOf("$") >= 0 ? Filter.optionsRegExp.exec(text) : null;
+ if (match)
+ {
+ options = match[1].toUpperCase().split(",");
+ text = match.input.substr(0, match.index);
+ for (var _loopIndex2 = 0; _loopIndex2 < options.length; ++_loopIndex2)
+ {
+ var option = options[_loopIndex2];
+ var value = null;
+ var separatorIndex = option.indexOf("=");
+ if (separatorIndex >= 0)
+ {
+ value = option.substr(separatorIndex + 1);
+ option = option.substr(0, separatorIndex);
+ }
+ option = option.replace(/-/, "_");
+ if (option in RegExpFilter.typeMap)
+ {
+ if (contentType == null)
+ {
+ contentType = 0;
+ }
+ contentType |= RegExpFilter.typeMap[option];
+ }
+ else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap)
+ {
+ if (contentType == null)
+ {
+ contentType = RegExpFilter.prototype.contentType;
+ }
+ contentType &= ~RegExpFilter.typeMap[option.substr(1)];
+ }
+ else if (option == "MATCH_CASE")
+ {
+ matchCase = true;
+ }
+ else if (option == "DOMAIN" && typeof value != "undefined")
+ {
+ domains = value;
+ }
+ else if (option == "THIRD_PARTY")
+ {
+ thirdParty = true;
+ }
+ else if (option == "~THIRD_PARTY")
+ {
+ thirdParty = false;
+ }
+ else if (option == "COLLAPSE")
+ {
+ collapse = true;
+ }
+ else if (option == "~COLLAPSE")
+ {
+ collapse = false;
+ }
+ else if (option == "SITEKEY" && typeof value != "undefined")
+ {
+ siteKeys = value.split(/\|/);
+ }
+ }
+ }
+ if (!blocking && (contentType == null || contentType & RegExpFilter.typeMap.DOCUMENT) && (!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text))
+ {
+ if (contentType == null)
+ {
+ contentType = RegExpFilter.prototype.contentType;
+ }
+ contentType &= ~RegExpFilter.typeMap.DOCUMENT;
+ }
+ if (!blocking && siteKeys)
+ {
+ contentType = RegExpFilter.typeMap.DOCUMENT;
+ }
+ try
+ {
+ if (blocking)
+ {
+ return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, collapse);
+ }
+ else
+ {
+ return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty, siteKeys);
+ }
+ }
+ catch (e)
+ {
+ return new InvalidFilter(text, e);
+ }
+ };
+ RegExpFilter.typeMap =
+ {
+ OTHER: 1,
+ SCRIPT: 2,
+ IMAGE: 4,
+ STYLESHEET: 8,
+ OBJECT: 16,
+ SUBDOCUMENT: 32,
+ DOCUMENT: 64,
+ XBL: 1,
+ PING: 1,
+ XMLHTTPREQUEST: 2048,
+ OBJECT_SUBREQUEST: 4096,
+ DTD: 1,
+ MEDIA: 16384,
+ FONT: 32768,
+ BACKGROUND: 4,
+ POPUP: 268435456,
+ DONOTTRACK: 536870912,
+ ELEMHIDE: 1073741824
+ };
+ RegExpFilter.prototype.contentType &= ~ (RegExpFilter.typeMap.ELEMHIDE | RegExpFilter.typeMap.DONOTTRACK | RegExpFilter.typeMap.POPUP);
+
+ function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, collapse)
+ {
+ RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
+ this.collapse = collapse;
+ }
+ exports.BlockingFilter = BlockingFilter;
+ BlockingFilter.prototype =
+ {
+ __proto__: RegExpFilter.prototype,
+ collapse: null
+ };
+
+ function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, siteKeys)
+ {
+ RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
+ if (siteKeys != null)
+ {
+ this.siteKeys = siteKeys;
+ }
+ }
+ exports.WhitelistFilter = WhitelistFilter;
+ WhitelistFilter.prototype =
+ {
+ __proto__: RegExpFilter.prototype,
+ siteKeys: null
+ };
+
+ function ElemHideBase(text, domains, selector)
+ {
+ ActiveFilter.call(this, text, domains ? domains.toUpperCase() : null);
+ if (domains)
+ {
+ this.selectorDomain = domains.replace(/,~[^,]+/g, "").replace(/^~[^,]+,?/, "").toLowerCase();
+ }
+ this.selector = selector;
+ }
+ exports.ElemHideBase = ElemHideBase;
+ ElemHideBase.prototype =
+ {
+ __proto__: ActiveFilter.prototype,
+ domainSeparator: ",",
+ ignoreTrailingDot: false,
+ selectorDomain: null,
+ selector: null
+ };
+ ElemHideBase.fromText = function(text, domain, isException, tagName, attrRules, selector)
+ {
+ if (!selector)
+ {
+ if (tagName == "*")
+ {
+ tagName = "";
+ }
+ var id = null;
+ var additional = "";
+ if (attrRules)
+ {
+ attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g);
+ for (var _loopIndex3 = 0; _loopIndex3 < attrRules.length; ++_loopIndex3)
+ {
+ var rule = attrRules[_loopIndex3];
+ rule = rule.substr(1, rule.length - 2);
+ var separatorPos = rule.indexOf("=");
+ if (separatorPos > 0)
+ {
+ rule = rule.replace(/=/, "=\"") + "\"";
+ additional += "[" + rule + "]";
+ }
+ else
+ {
+ if (id)
+ {
+ var Utils = require("utils").Utils;
+ return new InvalidFilter(text, Utils.getString("filter_elemhide_duplicate_id"));
+ }
+ else
+ {
+ id = rule;
+ }
+ }
+ }
+ }
+ if (id)
+ {
+ selector = tagName + "." + id + additional + "," + tagName + "#" + id + additional;
+ }
+ else if (tagName || additional)
+ {
+ selector = tagName + additional;
+ }
+ else
+ {
+ var Utils = require("utils").Utils;
+ return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria"));
+ }
+ }
+ if (isException)
+ {
+ return new ElemHideException(text, domain, selector);
+ }
+ else
+ {
+ return new ElemHideFilter(text, domain, selector);
+ }
+ };
+
+ function ElemHideFilter(text, domains, selector)
+ {
+ ElemHideBase.call(this, text, domains, selector);
+ }
+ exports.ElemHideFilter = ElemHideFilter;
+ ElemHideFilter.prototype =
+ {
+ __proto__: ElemHideBase.prototype
+ };
+
+ function ElemHideException(text, domains, selector)
+ {
+ ElemHideBase.call(this, text, domains, selector);
+ }
+ exports.ElemHideException = ElemHideException;
+ ElemHideException.prototype =
+ {
+ __proto__: ElemHideBase.prototype
+ };
+ return exports;
+})();
+require.scopes["subscriptionClasses"] = (function()
+{
+ var exports = {};
+ var _tempVar4 = require("filterClasses");
+ var ActiveFilter = _tempVar4.ActiveFilter;
+ var BlockingFilter = _tempVar4.BlockingFilter;
+ var WhitelistFilter = _tempVar4.WhitelistFilter;
+ var ElemHideBase = _tempVar4.ElemHideBase;
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+
+ function Subscription(url, title)
+ {
+ this.url = url;
+ this.filters = [];
+ if (title)
+ {
+ this._title = title;
+ }
+ else
+ {
+ var Utils = require("utils").Utils;
+ this._title = Utils.getString("newGroup_title");
+ }
+ Subscription.knownSubscriptions[url] = this;
+ }
+ exports.Subscription = Subscription;
+ Subscription.prototype =
+ {
+ url: null,
+ filters: null,
+ _title: null,
+ _fixedTitle: false,
+ _disabled: false,
+ get title()
+ {
+ return this._title;
+ },
+ set title(value)
+ {
+ if (value != this._title)
+ {
+ var oldValue = this._title;
+ this._title = value;
+ FilterNotifier.triggerListeners("subscription.title", this, value, oldValue);
+ }
+ return this._title;
+ },
+ get fixedTitle()
+ {
+ return this._fixedTitle;
+ },
+ set fixedTitle(value)
+ {
+ if (value != this._fixedTitle)
+ {
+ var oldValue = this._fixedTitle;
+ this._fixedTitle = value;
+ FilterNotifier.triggerListeners("subscription.fixedTitle", this, value, oldValue);
+ }
+ return this._fixedTitle;
+ },
+ get disabled()
+ {
+ return this._disabled;
+ },
+ set disabled(value)
+ {
+ if (value != this._disabled)
+ {
+ var oldValue = this._disabled;
+ this._disabled = value;
+ FilterNotifier.triggerListeners("subscription.disabled", this, value, oldValue);
+ }
+ return this._disabled;
+ },
+ serialize: function(buffer)
+ {
+ buffer.push("[Subscription]");
+ buffer.push("url=" + this.url);
+ buffer.push("title=" + this._title);
+ if (this._fixedTitle)
+ {
+ buffer.push("fixedTitle=true");
+ }
+ if (this._disabled)
+ {
+ buffer.push("disabled=true");
+ }
+ },
+ serializeFilters: function(buffer)
+ {
+ for (var _loopIndex5 = 0; _loopIndex5 < this.filters.length; ++_loopIndex5)
+ {
+ var filter = this.filters[_loopIndex5];
+ buffer.push(filter.text.replace(/\[/g, "\\["));
+ }
+ },
+ toString: function()
+ {
+ var buffer = [];
+ this.serialize(buffer);
+ return buffer.join("\n");
+ }
+ };
+ Subscription.knownSubscriptions =
+ {
+ __proto__: null
+ };
+ Subscription.fromURL = function(url)
+ {
+ if (url in Subscription.knownSubscriptions)
+ {
+ return Subscription.knownSubscriptions[url];
+ }
+ try
+ {
+ url = Services.io.newURI(url, null, null).spec;
+ return new DownloadableSubscription(url, null);
+ }
+ catch (e)
+ {
+ return new SpecialSubscription(url);
+ }
+ };
+ Subscription.fromObject = function(obj)
+ {
+ var result;
+ try
+ {
+ obj.url = Services.io.newURI(obj.url, null, null).spec;
+ result = new DownloadableSubscription(obj.url, obj.title);
+ if ("nextURL" in obj)
+ {
+ result.nextURL = obj.nextURL;
+ }
+ if ("downloadStatus" in obj)
+ {
+ result._downloadStatus = obj.downloadStatus;
+ }
+ if ("lastModified" in obj)
+ {
+ result.lastModified = obj.lastModified;
+ }
+ if ("lastSuccess" in obj)
+ {
+ result.lastSuccess = parseInt(obj.lastSuccess) || 0;
+ }
+ if ("lastCheck" in obj)
+ {
+ result._lastCheck = parseInt(obj.lastCheck) || 0;
+ }
+ if ("expires" in obj)
+ {
+ result.expires = parseInt(obj.expires) || 0;
+ }
+ if ("softExpiration" in obj)
+ {
+ result.softExpiration = parseInt(obj.softExpiration) || 0;
+ }
+ if ("errors" in obj)
+ {
+ result._errors = parseInt(obj.errors) || 0;
+ }
+ if ("requiredVersion" in obj)
+ {
+ var addonVersion = require("info").addonVersion;
+ result.requiredVersion = obj.requiredVersion;
+ if (Services.vc.compare(result.requiredVersion, addonVersion) > 0)
+ {
+ result.upgradeRequired = true;
+ }
+ }
+ if ("alternativeLocations" in obj)
+ {
+ result.alternativeLocations = obj.alternativeLocations;
+ }
+ if ("homepage" in obj)
+ {
+ result._homepage = obj.homepage;
+ }
+ if ("lastDownload" in obj)
+ {
+ result._lastDownload = parseInt(obj.lastDownload) || 0;
+ }
+ }
+ catch (e)
+ {
+ if (!("title" in obj))
+ {
+ if (obj.url == "~wl~")
+ {
+ obj.defaults = "whitelist";
+ }
+ else if (obj.url == "~fl~")
+ {
+ obj.defaults = "blocking";
+ }
+ else if (obj.url == "~eh~")
+ {
+ obj.defaults = "elemhide";
+ }
+ if ("defaults" in obj)
+ {
+ var Utils = require("utils").Utils;
+ obj.title = Utils.getString(obj.defaults + "Group_title");
+ }
+ }
+ result = new SpecialSubscription(obj.url, obj.title);
+ if ("defaults" in obj)
+ {
+ result.defaults = obj.defaults.split(" ");
+ }
+ }
+ if ("fixedTitle" in obj)
+ {
+ result._fixedTitle = obj.fixedTitle == "true";
+ }
+ if ("disabled" in obj)
+ {
+ result._disabled = obj.disabled == "true";
+ }
+ return result;
+ };
+
+ function SpecialSubscription(url, title)
+ {
+ Subscription.call(this, url, title);
+ }
+ exports.SpecialSubscription = SpecialSubscription;
+ SpecialSubscription.prototype =
+ {
+ __proto__: Subscription.prototype,
+ defaults: null,
+ isDefaultFor: function(filter)
+ {
+ if (this.defaults && this.defaults.length)
+ {
+ for (var _loopIndex6 = 0; _loopIndex6 < this.defaults.length; ++_loopIndex6)
+ {
+ var type = this.defaults[_loopIndex6];
+ if (filter instanceof SpecialSubscription.defaultsMap[type])
+ {
+ return true;
+ }
+ if (!(filter instanceof ActiveFilter) && type == "blacklist")
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ serialize: function(buffer)
+ {
+ Subscription.prototype.serialize.call(this, buffer);
+ if (this.defaults && this.defaults.length)
+ {
+ buffer.push("defaults=" + this.defaults.filter(function(type)
+ {
+ return type in SpecialSubscription.defaultsMap;
+ }).join(" "));
+ }
+ if (this._lastDownload)
+ {
+ buffer.push("lastDownload=" + this._lastDownload);
+ }
+ }
+ };
+ SpecialSubscription.defaultsMap =
+ {
+ __proto__: null,
+ "whitelist": WhitelistFilter,
+ "blocking": BlockingFilter,
+ "elemhide": ElemHideBase
+ };
+ SpecialSubscription.create = function(title)
+ {
+ var url;
+ do
+ {
+ url = "~user~" + Math.round(Math.random() * 1000000);
+ }
+ while (url in Subscription.knownSubscriptions);
+ return new SpecialSubscription(url, title);
+ };
+ SpecialSubscription.createForFilter = function(filter)
+ {
+ var subscription = SpecialSubscription.create();
+ subscription.filters.push(filter);
+ for (var type in SpecialSubscription.defaultsMap)
+ {
+ if (filter instanceof SpecialSubscription.defaultsMap[type])
+ {
+ subscription.defaults = [type];
+ }
+ }
+ if (!subscription.defaults)
+ {
+ subscription.defaults = ["blocking"];
+ }
+ var Utils = require("utils").Utils;
+ subscription.title = Utils.getString(subscription.defaults[0] + "Group_title");
+ return subscription;
+ };
+
+ function RegularSubscription(url, title)
+ {
+ Subscription.call(this, url, title || url);
+ }
+ exports.RegularSubscription = RegularSubscription;
+ RegularSubscription.prototype =
+ {
+ __proto__: Subscription.prototype,
+ _homepage: null,
+ _lastDownload: 0,
+ get homepage()
+ {
+ return this._homepage;
+ },
+ set homepage(value)
+ {
+ if (value != this._homepage)
+ {
+ var oldValue = this._homepage;
+ this._homepage = value;
+ FilterNotifier.triggerListeners("subscription.homepage", this, value, oldValue);
+ }
+ return this._homepage;
+ },
+ get lastDownload()
+ {
+ return this._lastDownload;
+ },
+ set lastDownload(value)
+ {
+ if (value != this._lastDownload)
+ {
+ var oldValue = this._lastDownload;
+ this._lastDownload = value;
+ FilterNotifier.triggerListeners("subscription.lastDownload", this, value, oldValue);
+ }
+ return this._lastDownload;
+ },
+ serialize: function(buffer)
+ {
+ Subscription.prototype.serialize.call(this, buffer);
+ if (this._homepage)
+ {
+ buffer.push("homepage=" + this._homepage);
+ }
+ if (this._lastDownload)
+ {
+ buffer.push("lastDownload=" + this._lastDownload);
+ }
+ }
+ };
+
+ function ExternalSubscription(url, title)
+ {
+ RegularSubscription.call(this, url, title);
+ }
+ exports.ExternalSubscription = ExternalSubscription;
+ ExternalSubscription.prototype =
+ {
+ __proto__: RegularSubscription.prototype,
+ serialize: function(buffer)
+ {
+ throw new Error("Unexpected call, external subscriptions should not be serialized");
+ }
+ };
+
+ function DownloadableSubscription(url, title)
+ {
+ RegularSubscription.call(this, url, title);
+ }
+ exports.DownloadableSubscription = DownloadableSubscription;
+ DownloadableSubscription.prototype =
+ {
+ __proto__: RegularSubscription.prototype,
+ _downloadStatus: null,
+ _lastCheck: 0,
+ _errors: 0,
+ nextURL: null,
+ get downloadStatus()
+ {
+ return this._downloadStatus;
+ },
+ set downloadStatus(value)
+ {
+ var oldValue = this._downloadStatus;
+ this._downloadStatus = value;
+ FilterNotifier.triggerListeners("subscription.downloadStatus", this, value, oldValue);
+ return this._downloadStatus;
+ },
+ lastModified: null,
+ lastSuccess: 0,
+ get lastCheck()
+ {
+ return this._lastCheck;
+ },
+ set lastCheck(value)
+ {
+ if (value != this._lastCheck)
+ {
+ var oldValue = this._lastCheck;
+ this._lastCheck = value;
+ FilterNotifier.triggerListeners("subscription.lastCheck", this, value, oldValue);
+ }
+ return this._lastCheck;
+ },
+ expires: 0,
+ softExpiration: 0,
+ get errors()
+ {
+ return this._errors;
+ },
+ set errors(value)
+ {
+ if (value != this._errors)
+ {
+ var oldValue = this._errors;
+ this._errors = value;
+ FilterNotifier.triggerListeners("subscription.errors", this, value, oldValue);
+ }
+ return this._errors;
+ },
+ requiredVersion: null,
+ upgradeRequired: false,
+ alternativeLocations: null,
+ serialize: function(buffer)
+ {
+ RegularSubscription.prototype.serialize.call(this, buffer);
+ if (this.nextURL)
+ {
+ buffer.push("nextURL=" + this.nextURL);
+ }
+ if (this.downloadStatus)
+ {
+ buffer.push("downloadStatus=" + this.downloadStatus);
+ }
+ if (this.lastModified)
+ {
+ buffer.push("lastModified=" + this.lastModified);
+ }
+ if (this.lastSuccess)
+ {
+ buffer.push("lastSuccess=" + this.lastSuccess);
+ }
+ if (this.lastCheck)
+ {
+ buffer.push("lastCheck=" + this.lastCheck);
+ }
+ if (this.expires)
+ {
+ buffer.push("expires=" + this.expires);
+ }
+ if (this.softExpiration)
+ {
+ buffer.push("softExpiration=" + this.softExpiration);
+ }
+ if (this.errors)
+ {
+ buffer.push("errors=" + this.errors);
+ }
+ if (this.requiredVersion)
+ {
+ buffer.push("requiredVersion=" + this.requiredVersion);
+ }
+ if (this.alternativeLocations)
+ {
+ buffer.push("alternativeLocations=" + this.alternativeLocations);
+ }
+ }
+ };
+ return exports;
+})();
+require.scopes["filterStorage"] = (function()
+{
+ var exports = {};
+ var IO = require("io").IO;
+ var Prefs = require("prefs").Prefs;
+ var _tempVar7 = require("filterClasses");
+ var Filter = _tempVar7.Filter;
+ var ActiveFilter = _tempVar7.ActiveFilter;
+ var _tempVar8 = require("subscriptionClasses");
+ var Subscription = _tempVar8.Subscription;
+ var SpecialSubscription = _tempVar8.SpecialSubscription;
+ var ExternalSubscription = _tempVar8.ExternalSubscription;
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+ var formatVersion = 4;
+ var FilterStorage = exports.FilterStorage =
+ {
+ get formatVersion()
+ {
+ return formatVersion;
+ },
+ get sourceFile()
+ {
+ var file = null;
+ if (Prefs.patternsfile)
+ {
+ file = IO.resolveFilePath(Prefs.patternsfile);
+ }
+ if (!file)
+ {
+ file = IO.resolveFilePath(Prefs.data_directory);
+ if (file)
+ {
+ file.append("patterns.ini");
+ }
+ }
+ if (!file)
+ {
+ try
+ {
+ file = IO.resolveFilePath(Services.prefs.getDefaultBranch("extensions.adblockplus.").getCharPref("data_directory"));
+ if (file)
+ {
+ file.append("patterns.ini");
+ }
+ }
+ catch (e){}
+ }
+ if (!file)
+ {
+ Cu.reportError("Adblock Plus: Failed to resolve filter file location from extensions.adblockplus.patternsfile preference");
+ }
+ this.__defineGetter__("sourceFile", function()
+ {
+ return file;
+ });
+ return this.sourceFile;
+ },
+ fileProperties:
+ {
+ __proto__: null
+ },
+ subscriptions: [],
+ knownSubscriptions:
+ {
+ __proto__: null
+ },
+ getGroupForFilter: function(filter)
+ {
+ var generalSubscription = null;
+ for (var _loopIndex9 = 0; _loopIndex9 < FilterStorage.subscriptions.length; ++_loopIndex9)
+ {
+ var subscription = FilterStorage.subscriptions[_loopIndex9];
+ if (subscription instanceof SpecialSubscription && !subscription.disabled)
+ {
+ if (subscription.isDefaultFor(filter))
+ {
+ return subscription;
+ }
+ if (!generalSubscription && (!subscription.defaults || !subscription.defaults.length))
+ {
+ generalSubscription = subscription;
+ }
+ }
+ }
+ return generalSubscription;
+ },
+ addSubscription: function(subscription, silent)
+ {
+ if (subscription.url in FilterStorage.knownSubscriptions)
+ {
+ return;
+ }
+ FilterStorage.subscriptions.push(subscription);
+ FilterStorage.knownSubscriptions[subscription.url] = subscription;
+ addSubscriptionFilters(subscription);
+ if (!silent)
+ {
+ FilterNotifier.triggerListeners("subscription.added", subscription);
+ }
+ },
+ removeSubscription: function(subscription, silent)
+ {
+ for (var i = 0; i < FilterStorage.subscriptions.length; i++)
+ {
+ if (FilterStorage.subscriptions[i].url == subscription.url)
+ {
+ removeSubscriptionFilters(subscription);
+ FilterStorage.subscriptions.splice(i--, 1);
+ delete FilterStorage.knownSubscriptions[subscription.url];
+ if (!silent)
+ {
+ FilterNotifier.triggerListeners("subscription.removed", subscription);
+ }
+ return;
+ }
+ }
+ },
+ moveSubscription: function(subscription, insertBefore)
+ {
+ var currentPos = FilterStorage.subscriptions.indexOf(subscription);
+ if (currentPos < 0)
+ {
+ return;
+ }
+ var newPos = insertBefore ? FilterStorage.subscriptions.indexOf(insertBefore) : -1;
+ if (newPos < 0)
+ {
+ newPos = FilterStorage.subscriptions.length;
+ }
+ if (currentPos < newPos)
+ {
+ newPos--;
+ }
+ if (currentPos == newPos)
+ {
+ return;
+ }
+ FilterStorage.subscriptions.splice(currentPos, 1);
+ FilterStorage.subscriptions.splice(newPos, 0, subscription);
+ FilterNotifier.triggerListeners("subscription.moved", subscription);
+ },
+ updateSubscriptionFilters: function(subscription, filters)
+ {
+ removeSubscriptionFilters(subscription);
+ subscription.oldFilters = subscription.filters;
+ subscription.filters = filters;
+ addSubscriptionFilters(subscription);
+ FilterNotifier.triggerListeners("subscription.updated", subscription);
+ delete subscription.oldFilters;
+ },
+ addFilter: function(filter, subscription, position, silent)
+ {
+ if (!subscription)
+ {
+ if (filter.subscriptions.some(function(s)
+ {
+ return s instanceof SpecialSubscription && !s.disabled;
+ }))
+ {
+ return;
+ }
+ subscription = FilterStorage.getGroupForFilter(filter);
+ }
+ if (!subscription)
+ {
+ subscription = SpecialSubscription.createForFilter(filter);
+ this.addSubscription(subscription);
+ return;
+ }
+ if (typeof position == "undefined")
+ {
+ position = subscription.filters.length;
+ }
+ if (filter.subscriptions.indexOf(subscription) < 0)
+ {
+ filter.subscriptions.push(subscription);
+ }
+ subscription.filters.splice(position, 0, filter);
+ if (!silent)
+ {
+ FilterNotifier.triggerListeners("filter.added", filter, subscription, position);
+ }
+ },
+ removeFilter: function(filter, subscription, position)
+ {
+ var subscriptions = subscription ? [subscription] : filter.subscriptions.slice();
+ for (var i = 0; i < subscriptions.length; i++)
+ {
+ var subscription = subscriptions[i];
+ if (subscription instanceof SpecialSubscription)
+ {
+ var positions = [];
+ if (typeof position == "undefined")
+ {
+ var index = -1;
+ do
+ {
+ index = subscription.filters.indexOf(filter, index + 1);
+ if (index >= 0)
+ {
+ positions.push(index);
+ }
+ }
+ while (index >= 0);
+ }
+ else
+ {
+ positions.push(position);
+ }
+ for (var j = positions.length - 1; j >= 0; j--)
+ {
+ var position = positions[j];
+ if (subscription.filters[position] == filter)
+ {
+ subscription.filters.splice(position, 1);
+ if (subscription.filters.indexOf(filter) < 0)
+ {
+ var index = filter.subscriptions.indexOf(subscription);
+ if (index >= 0)
+ {
+ filter.subscriptions.splice(index, 1);
+ }
+ }
+ FilterNotifier.triggerListeners("filter.removed", filter, subscription, position);
+ }
+ }
+ }
+ }
+ },
+ moveFilter: function(filter, subscription, oldPosition, newPosition)
+ {
+ if (!(subscription instanceof SpecialSubscription) || subscription.filters[oldPosition] != filter)
+ {
+ return;
+ }
+ newPosition = Math.min(Math.max(newPosition, 0), subscription.filters.length - 1);
+ if (oldPosition == newPosition)
+ {
+ return;
+ }
+ subscription.filters.splice(oldPosition, 1);
+ subscription.filters.splice(newPosition, 0, filter);
+ FilterNotifier.triggerListeners("filter.moved", filter, subscription, oldPosition, newPosition);
+ },
+ increaseHitCount: function(filter)
+ {
+ if (!Prefs.savestats || PrivateBrowsing.enabled || !(filter instanceof ActiveFilter))
+ {
+ return;
+ }
+ filter.hitCount++;
+ filter.lastHit = Date.now();
+ },
+ resetHitCounts: function(filters)
+ {
+ if (!filters)
+ {
+ filters = [];
+ for (var text in Filter.knownFilters)
+ {
+ filters.push(Filter.knownFilters[text]);
+ }
+ }
+ for (var _loopIndex10 = 0; _loopIndex10 < filters.length; ++_loopIndex10)
+ {
+ var filter = filters[_loopIndex10];
+ filter.hitCount = 0;
+ filter.lastHit = 0;
+ }
+ },
+ _loading: false,
+ loadFromDisk: function(sourceFile)
+ {
+ if (this._loading)
+ {
+ return;
+ }
+ var readFile = function(sourceFile, backupIndex)
+ {
+ var parser = new INIParser();
+ doneReading(parser);
+ return;
+
+ IO.readFromFile(sourceFile, true, parser, function(e)
+ {
+ if (!e && parser.subscriptions.length == 0)
+ {
+ e = new Error("No data in the file");
+ }
+ if (e)
+ {
+ Cu.reportError(e);
+ }
+ if (e && !explicitFile)
+ {
+ sourceFile = this.sourceFile;
+ if (sourceFile)
+ {
+ var _tempVar11 = /^(.*)(\.\w+)$/.exec(sourceFile.leafName) || [null, sourceFile.leafName, ""];
+ var part1 = _tempVar11[1];
+ var part2 = _tempVar11[2];
+ sourceFile = sourceFile.clone();
+ sourceFile.leafName = part1 + "-backup" + ++backupIndex + part2;
+ IO.statFile(sourceFile, function(e, statData)
+ {
+ if (!e && statData.exists)
+ {
+ readFile(sourceFile, backupIndex);
+ }
+ else
+ {
+ doneReading(parser);
+ }
+ });
+ return;
+ }
+ }
+ doneReading(parser);
+ }.bind(this), "FilterStorageRead");
+ }.bind(this);
+ var doneReading = function(parser)
+ {
+ var specialMap =
+ {
+ "~il~": true,
+ "~wl~": true,
+ "~fl~": true,
+ "~eh~": true
+ };
+ var knownSubscriptions =
+ {
+ __proto__: null
+ };
+ for (var i = 0; i < parser.subscriptions.length; i++)
+ {
+ var subscription = parser.subscriptions[i];
+ if (subscription instanceof SpecialSubscription && subscription.filters.length == 0 && subscription.url in specialMap)
+ {
+ parser.subscriptions.splice(i--, 1);
+ }
+ else
+ {
+ knownSubscriptions[subscription.url] = subscription;
+ }
+ }
+ this.fileProperties = parser.fileProperties;
+ this.subscriptions = parser.subscriptions;
+ this.knownSubscriptions = knownSubscriptions;
+ Filter.knownFilters = parser.knownFilters;
+ Subscription.knownSubscriptions = parser.knownSubscriptions;
+ if (parser.userFilters)
+ {
+ for (var i = 0; i < parser.userFilters.length; i++)
+ {
+ var filter = Filter.fromText(parser.userFilters[i]);
+ this.addFilter(filter, null, undefined, true);
+ }
+ }
+ this._loading = false;
+ FilterNotifier.triggerListeners("load");
+ if (sourceFile != this.sourceFile)
+ {
+ this.saveToDisk();
+ }
+ }.bind(this);
+ var startRead = function(file)
+ {
+ this._loading = true;
+ readFile(file, 0);
+ }.bind(this);
+ var explicitFile;
+ if (sourceFile)
+ {
+ explicitFile = true;
+ startRead(sourceFile);
+ }
+ else
+ {
+ explicitFile = false;
+ sourceFile = FilterStorage.sourceFile;
+ var callback = function(e, statData)
+ {
+ if (e || !statData.exists)
+ {
+ var addonRoot = require("info").addonRoot;
+ sourceFile = Services.io.newURI(addonRoot + "defaults/patterns.ini", null, null);
+ }
+ startRead(sourceFile);
+ };
+ if (sourceFile)
+ {
+ IO.statFile(sourceFile, callback);
+ }
+ else
+ {
+ callback(true);
+ }
+ }
+ },
+ _generateFilterData: function(subscriptions)
+ {
+ var _generatorResult12 = [];
+ _generatorResult12.push("# Adblock Plus preferences");
+ _generatorResult12.push("version=" + formatVersion);
+ var saved =
+ {
+ __proto__: null
+ };
+ var buf = [];
+ for (var i = 0; i < subscriptions.length; i++)
+ {
+ var subscription = subscriptions[i];
+ for (var j = 0; j < subscription.filters.length; j++)
+ {
+ var filter = subscription.filters[j];
+ if (!(filter.text in saved))
+ {
+ filter.serialize(buf);
+ saved[filter.text] = filter;
+ for (var k = 0; k < buf.length; k++)
+ {
+ _generatorResult12.push(buf[k]);
+ }
+ buf.splice(0);
+ }
+ }
+ }
+ for (var i = 0; i < subscriptions.length; i++)
+ {
+ var subscription = subscriptions[i];
+ _generatorResult12.push("");
+ subscription.serialize(buf);
+ if (subscription.filters.length)
+ {
+ buf.push("", "[Subscription filters]");
+ subscription.serializeFilters(buf);
+ }
+ for (var k = 0; k < buf.length; k++)
+ {
+ _generatorResult12.push(buf[k]);
+ }
+ buf.splice(0);
+ }
+ return _generatorResult12;
+ },
+ _saving: false,
+ _needsSave: false,
+ saveToDisk: function(targetFile)
+ {
+ var explicitFile = true;
+ if (!targetFile)
+ {
+ targetFile = FilterStorage.sourceFile;
+ explicitFile = false;
+ }
+ if (!targetFile)
+ {
+ return;
+ }
+ if (!explicitFile && this._saving)
+ {
+ this._needsSave = true;
+ return;
+ }
+ try
+ {
+ targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+ catch (e){}
+ var writeFilters = function()
+ {
+ IO.writeToFile(targetFile, true, this._generateFilterData(subscriptions), function(e)
+ {
+ /*if (!explicitFile)
+ {
+ this._saving = false;
+ }
+ if (e)
+ {
+ Cu.reportError(e);
+ }
+ if (!explicitFile && this._needsSave)
+ {
+ this._needsSave = false;
+ this.saveToDisk();
+ }
+ else
+ {
+ FilterNotifier.triggerListeners("save");
+ }*/
+ }.bind(this), "FilterStorageWrite");
+ }.bind(this);
+ var checkBackupRequired = function(callbackNotRequired, callbackRequired)
+ {
+ if (explicitFile || Prefs.patternsbackups <= 0)
+ {
+ callbackNotRequired();
+ }
+ else
+ {
+ IO.statFile(targetFile, function(e, statData)
+ {
+ if (e || !statData.exists)
+ {
+ callbackNotRequired();
+ }
+ else
+ {
+ var _tempVar13 = /^(.*)(\.\w+)$/.exec(targetFile.leafName) || [null, targetFile.leafName, ""];
+ var part1 = _tempVar13[1];
+ var part2 = _tempVar13[2];
+ var newestBackup = targetFile.clone();
+ newestBackup.leafName = part1 + "-backup1" + part2;
+ IO.statFile(newestBackup, function(e, statData)
+ {
+ if (!e && (!statData.exists || (Date.now() - statData.lastModified) / 3600000 >= Prefs.patternsbackupinterval))
+ {
+ callbackRequired(part1, part2);
+ }
+ else
+ {
+ callbackNotRequired();
+ }
+ });
+ }
+ });
+ }
+ }.bind(this);
+ var removeLastBackup = function(part1, part2)
+ {
+ var file = targetFile.clone();
+ file.leafName = part1 + "-backup" + Prefs.patternsbackups + part2;
+ IO.removeFile(file, function(e)
+ {
+ return renameBackup(part1, part2, Prefs.patternsbackups - 1);
+ });
+ }.bind(this);
+ var renameBackup = function(part1, part2, index)
+ {
+ if (index > 0)
+ {
+ var fromFile = targetFile.clone();
+ fromFile.leafName = part1 + "-backup" + index + part2;
+ var toName = part1 + "-backup" + (index + 1) + part2;
+ IO.renameFile(fromFile, toName, function(e)
+ {
+ return renameBackup(part1, part2, index - 1);
+ });
+ }
+ else
+ {
+ var toFile = targetFile.clone();
+ toFile.leafName = part1 + "-backup" + (index + 1) + part2;
+ IO.copyFile(targetFile, toFile, writeFilters);
+ }
+ }.bind(this);
+ var subscriptions = this.subscriptions.filter(function(s)
+ {
+ return !(s instanceof ExternalSubscription);
+ });
+ if (!explicitFile)
+ {
+ this._saving = true;
+ }
+ checkBackupRequired(writeFilters, removeLastBackup);
+ },
+ getBackupFiles: function()
+ {
+ var result = [];
+ var _tempVar14 = /^(.*)(\.\w+)$/.exec(FilterStorage.sourceFile.leafName) || [null, FilterStorage.sourceFile.leafName, ""];
+ var part1 = _tempVar14[1];
+ var part2 = _tempVar14[2];
+ for (var i = 1;; i++)
+ {
+ var file = FilterStorage.sourceFile.clone();
+ file.leafName = part1 + "-backup" + i + part2;
+ if (file.exists())
+ {
+ result.push(file);
+ }
+ else
+ {
+ break;
+ }
+ }
+ return result;
+ }
+ };
+
+ function addSubscriptionFilters(subscription)
+ {
+ if (!(subscription.url in FilterStorage.knownSubscriptions))
+ {
+ return;
+ }
+ for (var _loopIndex15 = 0; _loopIndex15 < subscription.filters.length; ++_loopIndex15)
+ {
+ var filter = subscription.filters[_loopIndex15];
+ filter.subscriptions.push(subscription);
+ }
+ }
+
+ function removeSubscriptionFilters(subscription)
+ {
+ if (!(subscription.url in FilterStorage.knownSubscriptions))
+ {
+ return;
+ }
+ for (var _loopIndex16 = 0; _loopIndex16 < subscription.filters.length; ++_loopIndex16)
+ {
+ var filter = subscription.filters[_loopIndex16];
+ var i = filter.subscriptions.indexOf(subscription);
+ if (i >= 0)
+ {
+ filter.subscriptions.splice(i, 1);
+ }
+ }
+ }
+ var PrivateBrowsing = exports.PrivateBrowsing =
+ {
+ enabled: false,
+ init: function()
+ {
+ if ("@mozilla.org/privatebrowsing;1" in Cc)
+ {
+ try
+ {
+ this.enabled = Cc["@mozilla.org/privatebrowsing;1"].getService(Ci.nsIPrivateBrowsingService).privateBrowsingEnabled;
+ Services.obs.addObserver(this, "private-browsing", true);
+ onShutdown.add(function()
+ {
+ Services.obs.removeObserver(this, "private-browsing");
+ }.bind(this));
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ }
+ },
+ observe: function(subject, topic, data)
+ {
+ if (topic == "private-browsing")
+ {
+ if (data == "enter")
+ {
+ this.enabled = true;
+ }
+ else if (data == "exit")
+ {
+ this.enabled = false;
+ }
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
+ };
+ PrivateBrowsing.init();
+
+ function INIParser()
+ {
+ this.fileProperties = this.curObj = {};
+ this.subscriptions = [];
+ this.knownFilters =
+ {
+ __proto__: null
+ };
+ this.knownSubscriptions =
+ {
+ __proto__: null
+ };
+ }
+ INIParser.prototype =
+ {
+ subscriptions: null,
+ knownFilters: null,
+ knownSubscrptions: null,
+ wantObj: true,
+ fileProperties: null,
+ curObj: null,
+ curSection: null,
+ userFilters: null,
+ process: function(val)
+ {
+ var origKnownFilters = Filter.knownFilters;
+ Filter.knownFilters = this.knownFilters;
+ var origKnownSubscriptions = Subscription.knownSubscriptions;
+ Subscription.knownSubscriptions = this.knownSubscriptions;
+ var match;
+ try
+ {
+ if (this.wantObj === true && (match = /^(\w+)=(.*)$/.exec(val)))
+ {
+ this.curObj[match[1]] = match[2];
+ }
+ else if (val === null || (match = /^\s*\[(.+)\]\s*$/.exec(val)))
+ {
+ if (this.curObj)
+ {
+ switch (this.curSection)
+ {
+ case "filter":
+ case "pattern":
+ if ("text" in this.curObj)
+ {
+ Filter.fromObject(this.curObj);
+ }
+ break;
+ case "subscription":
+ var subscription = Subscription.fromObject(this.curObj);
+ if (subscription)
+ {
+ this.subscriptions.push(subscription);
+ }
+ break;
+ case "subscription filters":
+ case "subscription patterns":
+ if (this.subscriptions.length)
+ {
+ var subscription = this.subscriptions[this.subscriptions.length - 1];
+ for (var _loopIndex17 = 0; _loopIndex17 < this.curObj.length; ++_loopIndex17)
+ {
+ var text = this.curObj[_loopIndex17];
+ var filter = Filter.fromText(text);
+ subscription.filters.push(filter);
+ filter.subscriptions.push(subscription);
+ }
+ }
+ break;
+ case "user patterns":
+ this.userFilters = this.curObj;
+ break;
+ }
+ }
+ if (val === null)
+ {
+ return;
+ }
+ this.curSection = match[1].toLowerCase();
+ switch (this.curSection)
+ {
+ case "filter":
+ case "pattern":
+ case "subscription":
+ this.wantObj = true;
+ this.curObj = {};
+ break;
+ case "subscription filters":
+ case "subscription patterns":
+ case "user patterns":
+ this.wantObj = false;
+ this.curObj = [];
+ break;
+ default:
+ this.wantObj = undefined;
+ this.curObj = null;
+ }
+ }
+ else if (this.wantObj === false && val)
+ {
+ this.curObj.push(val.replace(/\\\[/g, "["));
+ }
+ }
+ finally
+ {
+ Filter.knownFilters = origKnownFilters;
+ Subscription.knownSubscriptions = origKnownSubscriptions;
+ }
+ }
+ };
+ return exports;
+})();
+require.scopes["elemHide"] = (function()
+{
+ var exports = {};
+ var Utils = require("utils").Utils;
+ var IO = require("io").IO;
+ var Prefs = require("prefs").Prefs;
+ var ElemHideException = require("filterClasses").ElemHideException;
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+ var AboutHandler = require("elemHideHitRegistration").AboutHandler;
+ var filterByKey =
+ {
+ __proto__: null
+ };
+ var keyByFilter =
+ {
+ __proto__: null
+ };
+ var knownExceptions =
+ {
+ __proto__: null
+ };
+ var exceptions =
+ {
+ __proto__: null
+ };
+ var styleURL = null;
+ var ElemHide = exports.ElemHide =
+ {
+ isDirty: false,
+ applied: false,
+ init: function()
+ {
+ Prefs.addListener(function(name)
+ {
+ if (name == "enabled")
+ {
+ ElemHide.apply();
+ }
+ });
+ onShutdown.add(function()
+ {
+ ElemHide.unapply();
+ });
+ var styleFile = IO.resolveFilePath(Prefs.data_directory);
+ styleFile.append("elemhide.css");
+ styleURL = Services.io.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL);
+ },
+ clear: function()
+ {
+ filterByKey =
+ {
+ __proto__: null
+ };
+ keyByFilter =
+ {
+ __proto__: null
+ };
+ knownExceptions =
+ {
+ __proto__: null
+ };
+ exceptions =
+ {
+ __proto__: null
+ };
+ ElemHide.isDirty = false;
+ ElemHide.unapply();
+ },
+ add: function(filter)
+ {
+ if (filter instanceof ElemHideException)
+ {
+ if (filter.text in knownExceptions)
+ {
+ return;
+ }
+ var selector = filter.selector;
+ if (!(selector in exceptions))
+ {
+ exceptions[selector] = [];
+ }
+ exceptions[selector].push(filter);
+ knownExceptions[filter.text] = true;
+ }
+ else
+ {
+ if (filter.text in keyByFilter)
+ {
+ return;
+ }
+ var key;
+ do
+ {
+ key = Math.random().toFixed(15).substr(5);
+ }
+ while (key in filterByKey);
+ filterByKey[key] = filter;
+ keyByFilter[filter.text] = key;
+ ElemHide.isDirty = true;
+ }
+ },
+ remove: function(filter)
+ {
+ if (filter instanceof ElemHideException)
+ {
+ if (!(filter.text in knownExceptions))
+ {
+ return;
+ }
+ var list = exceptions[filter.selector];
+ var index = list.indexOf(filter);
+ if (index >= 0)
+ {
+ list.splice(index, 1);
+ }
+ delete knownExceptions[filter.text];
+ }
+ else
+ {
+ if (!(filter.text in keyByFilter))
+ {
+ return;
+ }
+ var key = keyByFilter[filter.text];
+ delete filterByKey[key];
+ delete keyByFilter[filter.text];
+ ElemHide.isDirty = true;
+ }
+ },
+ getException: function(filter, docDomain)
+ {
+ var selector = filter.selector;
+ if (!(filter.selector in exceptions))
+ {
+ return null;
+ }
+ var list = exceptions[filter.selector];
+ for (var i = list.length - 1; i >= 0; i--)
+ {
+ if (list[i].isActiveOnDomain(docDomain))
+ {
+ return list[i];
+ }
+ }
+ return null;
+ },
+ _applying: false,
+ _needsApply: false,
+ apply: function()
+ {
+ if (this._applying)
+ {
+ this._needsApply = true;
+ return;
+ }
+ if (!ElemHide.isDirty || !Prefs.enabled)
+ {
+ if (Prefs.enabled && !ElemHide.applied)
+ {
+ try
+ {
+ Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
+ ElemHide.applied = true;
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ }
+ else if (!Prefs.enabled && ElemHide.applied)
+ {
+ ElemHide.unapply();
+ }
+ return;
+ }
+ IO.writeToFile(styleURL.file, false, this._generateCSSContent(), function(e)
+ {
+ this._applying = false;
+ if (e && e.result == Cr.NS_ERROR_NOT_AVAILABLE)
+ {
+ IO.removeFile(styleURL.file, function(e2){});
+ }
+ else if (e)
+ {
+ Cu.reportError(e);
+ }
+ if (this._needsApply)
+ {
+ this._needsApply = false;
+ this.apply();
+ }
+ else if (!e || e.result == Cr.NS_ERROR_NOT_AVAILABLE)
+ {
+ ElemHide.isDirty = false;
+ ElemHide.unapply();
+ if (!e)
+ {
+ try
+ {
+ Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
+ ElemHide.applied = true;
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ }
+ FilterNotifier.triggerListeners("elemhideupdate");
+ }
+ }.bind(this), "ElemHideWrite");
+ this._applying = true;
+ },
+ _generateCSSContent: function()
+ {
+ var _generatorResult12 = [];
+ var domains =
+ {
+ __proto__: null
+ };
+ var hasFilters = false;
+ for (var key in filterByKey)
+ {
+ var filter = filterByKey[key];
+ var domain = filter.selectorDomain || "";
+ var list;
+ if (domain in domains)
+ {
+ list = domains[domain];
+ }
+ else
+ {
+ list =
+ {
+ __proto__: null
+ };
+ domains[domain] = list;
+ }
+ list[filter.selector] = key;
+ hasFilters = true;
+ }
+ if (!hasFilters)
+ {
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ }
+
+ function escapeChar(match)
+ {
+ return "\\" + match.charCodeAt(0).toString(16) + " ";
+ }
+ var cssTemplate = "-moz-binding: url(about:" + AboutHandler.aboutPrefix + "?%ID%#dummy) !important;";
+ for (var domain in domains)
+ {
+ var rules = [];
+ var list = domains[domain];
+ if (domain)
+ {
+ _generatorResult12.push(("@-moz-document domain(\"" + domain.split(",").join("\"),domain(\"") + "\"){").replace(/[^\x01-\x7F]/g, escapeChar));
+ }
+ else
+ {
+ _generatorResult12.push("@-moz-document url-prefix(\"http://\"),url-prefix(\"https://\")," + "url-prefix(\"mailbox://\"),url-prefix(\"imap://\")," + "url-prefix(\"news://\"),url-prefix(\"snews://\"){");
+ }
+ for (var selector in list)
+ {
+ _generatorResult12.push(selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate.replace("%ID%", list[selector]) + "}");
+ }
+ _generatorResult12.push("}");
+ }
+ return _generatorResult12;
+ },
+ unapply: function()
+ {
+ if (ElemHide.applied)
+ {
+ try
+ {
+ Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ ElemHide.applied = false;
+ }
+ },
+ get styleURL()
+ {
+ return ElemHide.applied ? styleURL.spec : null;
+ },
+ getFilterByKey: function(key)
+ {
+ return key in filterByKey ? filterByKey[key] : null;
+ },
+ getSelectorsForDomain: function(domain, specificOnly)
+ {
+ var result = [];
+ for (var key in filterByKey)
+ {
+ var filter = filterByKey[key];
+ if (specificOnly && (!filter.domains || filter.domains[""]))
+ {
+ continue;
+ }
+ if (filter.isActiveOnDomain(domain) && !this.getException(filter, domain))
+ {
+ result.push(filter.selector);
+ }
+ }
+ return result;
+ }
+ };
+ return exports;
+})();
+require.scopes["matcher"] = (function()
+{
+ var exports = {};
+ var _tempVar18 = require("filterClasses");
+ var Filter = _tempVar18.Filter;
+ var RegExpFilter = _tempVar18.RegExpFilter;
+ var WhitelistFilter = _tempVar18.WhitelistFilter;
+
+ function Matcher()
+ {
+ this.clear();
+ }
+ exports.Matcher = Matcher;
+ Matcher.prototype =
+ {
+ filterByKeyword: null,
+ keywordByFilter: null,
+ clear: function()
+ {
+ this.filterByKeyword =
+ {
+ __proto__: null
+ };
+ this.keywordByFilter =
+ {
+ __proto__: null
+ };
+ },
+ add: function(filter)
+ {
+ if (filter.text in this.keywordByFilter)
+ {
+ return;
+ }
+ var keyword = this.findKeyword(filter);
+ var oldEntry = this.filterByKeyword[keyword];
+ if (typeof oldEntry == "undefined")
+ {
+ this.filterByKeyword[keyword] = filter;
+ }
+ else if (oldEntry.length == 1)
+ {
+ this.filterByKeyword[keyword] = [oldEntry, filter];
+ }
+ else
+ {
+ oldEntry.push(filter);
+ }
+ this.keywordByFilter[filter.text] = keyword;
+ },
+ remove: function(filter)
+ {
+ if (!(filter.text in this.keywordByFilter))
+ {
+ return;
+ }
+ var keyword = this.keywordByFilter[filter.text];
+ var list = this.filterByKeyword[keyword];
+ if (list.length <= 1)
+ {
+ delete this.filterByKeyword[keyword];
+ }
+ else
+ {
+ var index = list.indexOf(filter);
+ if (index >= 0)
+ {
+ list.splice(index, 1);
+ if (list.length == 1)
+ {
+ this.filterByKeyword[keyword] = list[0];
+ }
+ }
+ }
+ delete this.keywordByFilter[filter.text];
+ },
+ findKeyword: function(filter)
+ {
+ var defaultResult = filter.contentType & RegExpFilter.typeMap.DONOTTRACK ? "donottrack" : "";
+ var text = filter.text;
+ if (Filter.regexpRegExp.test(text))
+ {
+ return defaultResult;
+ }
+ var match = Filter.optionsRegExp.exec(text);
+ if (match)
+ {
+ text = match.input.substr(0, match.index);
+ }
+ if (text.substr(0, 2) == "@@")
+ {
+ text = text.substr(2);
+ }
+ var candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
+ if (!candidates)
+ {
+ return defaultResult;
+ }
+ var hash = this.filterByKeyword;
+ var result = defaultResult;
+ var resultCount = 16777215;
+ var resultLength = 0;
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var candidate = candidates[i].substr(1);
+ var count = candidate in hash ? hash[candidate].length : 0;
+ if (count < resultCount || count == resultCount && candidate.length > resultLength)
+ {
+ result = candidate;
+ resultCount = count;
+ resultLength = candidate.length;
+ }
+ }
+ return result;
+ },
+ hasFilter: function(filter)
+ {
+ return filter.text in this.keywordByFilter;
+ },
+ getKeywordForFilter: function(filter)
+ {
+ if (filter.text in this.keywordByFilter)
+ {
+ return this.keywordByFilter[filter.text];
+ }
+ else
+ {
+ return null;
+ }
+ },
+ _checkEntryMatch: function(keyword, location, contentType, docDomain, thirdParty)
+ {
+ var list = this.filterByKeyword[keyword];
+ for (var i = 0; i < list.length; i++)
+ {
+ var filter = list[i];
+ if (filter.matches(location, contentType, docDomain, thirdParty))
+ {
+ return filter;
+ }
+ }
+ return null;
+ },
+ matchesAny: function(location, contentType, docDomain, thirdParty)
+ {
+ var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
+ if (candidates === null)
+ {
+ candidates = [];
+ }
+ if (contentType == "DONOTTRACK")
+ {
+ candidates.unshift("donottrack");
+ }
+ else
+ {
+ candidates.push("");
+ }
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var substr = candidates[i];
+ if (substr in this.filterByKeyword)
+ {
+ var result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
+ if (result)
+ {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+ };
+
+ function CombinedMatcher()
+ {
+ this.blacklist = new Matcher();
+ this.whitelist = new Matcher();
+ this.keys =
+ {
+ __proto__: null
+ };
+ this.resultCache =
+ {
+ __proto__: null
+ };
+ }
+ exports.CombinedMatcher = CombinedMatcher;
+ CombinedMatcher.maxCacheEntries = 1000;
+ CombinedMatcher.prototype =
+ {
+ blacklist: null,
+ whitelist: null,
+ keys: null,
+ resultCache: null,
+ cacheEntries: 0,
+ clear: function()
+ {
+ this.blacklist.clear();
+ this.whitelist.clear();
+ this.keys =
+ {
+ __proto__: null
+ };
+ this.resultCache =
+ {
+ __proto__: null
+ };
+ this.cacheEntries = 0;
+ },
+ add: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ if (filter.siteKeys)
+ {
+ for (var i = 0; i < filter.siteKeys.length; i++)
+ {
+ this.keys[filter.siteKeys[i]] = filter.text;
+ }
+ }
+ else
+ {
+ this.whitelist.add(filter);
+ }
+ }
+ else
+ {
+ this.blacklist.add(filter);
+ }
+ if (this.cacheEntries > 0)
+ {
+ this.resultCache =
+ {
+ __proto__: null
+ };
+ this.cacheEntries = 0;
+ }
+ },
+ remove: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ if (filter.siteKeys)
+ {
+ for (var i = 0; i < filter.siteKeys.length; i++)
+ {
+ delete this.keys[filter.siteKeys[i]];
+ }
+ }
+ else
+ {
+ this.whitelist.remove(filter);
+ }
+ }
+ else
+ {
+ this.blacklist.remove(filter);
+ }
+ if (this.cacheEntries > 0)
+ {
+ this.resultCache =
+ {
+ __proto__: null
+ };
+ this.cacheEntries = 0;
+ }
+ },
+ findKeyword: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.findKeyword(filter);
+ }
+ else
+ {
+ return this.blacklist.findKeyword(filter);
+ }
+ },
+ hasFilter: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.hasFilter(filter);
+ }
+ else
+ {
+ return this.blacklist.hasFilter(filter);
+ }
+ },
+ getKeywordForFilter: function(filter)
+ {
+ if (filter instanceof WhitelistFilter)
+ {
+ return this.whitelist.getKeywordForFilter(filter);
+ }
+ else
+ {
+ return this.blacklist.getKeywordForFilter(filter);
+ }
+ },
+ isSlowFilter: function(filter)
+ {
+ var matcher = filter instanceof WhitelistFilter ? this.whitelist : this.blacklist;
+ if (matcher.hasFilter(filter))
+ {
+ return !matcher.getKeywordForFilter(filter);
+ }
+ else
+ {
+ return !matcher.findKeyword(filter);
+ }
+ },
+ matchesAnyInternal: function(location, contentType, docDomain, thirdParty)
+ {
+ var candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
+ if (candidates === null)
+ {
+ candidates = [];
+ }
+ if (contentType == "DONOTTRACK")
+ {
+ candidates.unshift("donottrack");
+ }
+ else
+ {
+ candidates.push("");
+ }
+ var blacklistHit = null;
+ for (var i = 0, l = candidates.length; i < l; i++)
+ {
+ var substr = candidates[i];
+ if (substr in this.whitelist.filterByKeyword)
+ {
+ var result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
+ if (result)
+ {
+ return result;
+ }
+ }
+ if (substr in this.blacklist.filterByKeyword && blacklistHit === null)
+ {
+ blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
+ }
+ }
+ return blacklistHit;
+ },
+ matchesAny: function(location, contentType, docDomain, thirdParty)
+ {
+ var key = location + " " + contentType + " " + docDomain + " " + thirdParty;
+ if (key in this.resultCache)
+ {
+ return this.resultCache[key];
+ }
+ var result = this.matchesAnyInternal(location, contentType, docDomain, thirdParty);
+ if (this.cacheEntries >= CombinedMatcher.maxCacheEntries)
+ {
+ this.resultCache =
+ {
+ __proto__: null
+ };
+ this.cacheEntries = 0;
+ }
+ this.resultCache[key] = result;
+ this.cacheEntries++;
+ return result;
+ },
+ matchesByKey: function(location, key, docDomain)
+ {
+ key = key.toUpperCase();
+ if (key in this.keys)
+ {
+ var filter = Filter.knownFilters[this.keys[key]];
+ if (filter && filter.matches(location, "DOCUMENT", docDomain, false))
+ {
+ return filter;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+ };
+ var defaultMatcher = exports.defaultMatcher = new CombinedMatcher();
+ return exports;
+})();
+require.scopes["filterListener"] = (function()
+{
+ var exports = {};
+ var FilterStorage = require("filterStorage").FilterStorage;
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+ var ElemHide = require("elemHide").ElemHide;
+ var defaultMatcher = require("matcher").defaultMatcher;
+ var _tempVar19 = require("filterClasses");
+ var ActiveFilter = _tempVar19.ActiveFilter;
+ var RegExpFilter = _tempVar19.RegExpFilter;
+ var ElemHideBase = _tempVar19.ElemHideBase;
+ var Prefs = require("prefs").Prefs;
+ var batchMode = false;
+ var isDirty = 0;
+ var FilterListener = exports.FilterListener =
+ {
+ get batchMode()
+ {
+ return batchMode;
+ },
+ set batchMode(value)
+ {
+ batchMode = value;
+ flushElemHide();
+ },
+ setDirty: function(factor)
+ {
+ if (factor == 0 && isDirty > 0)
+ {
+ isDirty = 1;
+ }
+ else
+ {
+ isDirty += factor;
+ }
+ if (isDirty >= 1)
+ {
+ FilterStorage.saveToDisk();
+ }
+ }
+ };
+ var HistoryPurgeObserver =
+ {
+ observe: function(subject, topic, data)
+ {
+ if (topic == "browser:purge-session-history" && Prefs.clearStatsOnHistoryPurge)
+ {
+ FilterStorage.resetHitCounts();
+ FilterListener.setDirty(0);
+ Prefs.recentReports = [];
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
+ };
+
+ function init()
+ {
+ FilterNotifier.addListener(function(action, item, newValue, oldValue)
+ {
+ var match = /^(\w+)\.(.*)/.exec(action);
+ if (match && match[1] == "filter")
+ {
+ onFilterChange(match[2], item, newValue, oldValue);
+ }
+ else if (match && match[1] == "subscription")
+ {
+ onSubscriptionChange(match[2], item, newValue, oldValue);
+ }
+ else
+ {
+ onGenericChange(action, item);
+ }
+ });
+ var application = require("info").application;
+ if (application == "chrome")
+ {
+ flushElemHide = function(){};
+ }
+ else
+ {
+ ElemHide.init();
+ }
+ FilterStorage.loadFromDisk();
+ Services.obs.addObserver(HistoryPurgeObserver, "browser:purge-session-history", true);
+ onShutdown.add(function()
+ {
+ Services.obs.removeObserver(HistoryPurgeObserver, "browser:purge-session-history");
+ });
+ }
+ init();
+
+ function flushElemHide()
+ {
+ if (!batchMode && ElemHide.isDirty)
+ {
+ ElemHide.apply();
+ }
+ }
+
+ function addFilter(filter)
+ {
+ if (!(filter instanceof ActiveFilter) || filter.disabled)
+ {
+ return;
+ }
+ var hasEnabled = false;
+ for (var i = 0; i < filter.subscriptions.length; i++)
+ {
+ if (!filter.subscriptions[i].disabled)
+ {
+ hasEnabled = true;
+ }
+ }
+ if (!hasEnabled)
+ {
+ return;
+ }
+ if (filter instanceof RegExpFilter)
+ {
+ defaultMatcher.add(filter);
+ }
+ else if (filter instanceof ElemHideBase)
+ {
+ ElemHide.add(filter);
+ }
+ }
+
+ function removeFilter(filter)
+ {
+ if (!(filter instanceof ActiveFilter))
+ {
+ return;
+ }
+ if (!filter.disabled)
+ {
+ var hasEnabled = false;
+ for (var i = 0; i < filter.subscriptions.length; i++)
+ {
+ if (!filter.subscriptions[i].disabled)
+ {
+ hasEnabled = true;
+ }
+ }
+ if (hasEnabled)
+ {
+ return;
+ }
+ }
+ if (filter instanceof RegExpFilter)
+ {
+ defaultMatcher.remove(filter);
+ }
+ else if (filter instanceof ElemHideBase)
+ {
+ ElemHide.remove(filter);
+ }
+ }
+
+ function onSubscriptionChange(action, subscription, newValue, oldValue)
+ {
+ FilterListener.setDirty(1);
+ if (action != "added" && action != "removed" && action != "disabled" && action != "updated")
+ {
+ return;
+ }
+ if (action != "removed" && !(subscription.url in FilterStorage.knownSubscriptions))
+ {
+ return;
+ }
+ if ((action == "added" || action == "removed" || action == "updated") && subscription.disabled)
+ {
+ return;
+ }
+ if (action == "added" || action == "removed" || action == "disabled")
+ {
+ var method = action == "added" || action == "disabled" && newValue == false ? addFilter : removeFilter;
+ if (subscription.filters)
+ {
+ subscription.filters.forEach(method);
+ }
+ }
+ else if (action == "updated")
+ {
+ subscription.oldFilters.forEach(removeFilter);
+ subscription.filters.forEach(addFilter);
+ }
+ flushElemHide();
+ }
+
+ function onFilterChange(action, filter, newValue, oldValue)
+ {
+ if (action == "hitCount" || action == "lastHit")
+ {
+ FilterListener.setDirty(0.002);
+ }
+ else
+ {
+ FilterListener.setDirty(1);
+ }
+ if (action != "added" && action != "removed" && action != "disabled")
+ {
+ return;
+ }
+ if ((action == "added" || action == "removed") && filter.disabled)
+ {
+ return;
+ }
+ if (action == "added" || action == "disabled" && newValue == false)
+ {
+ addFilter(filter);
+ }
+ else
+ {
+ removeFilter(filter);
+ }
+ flushElemHide();
+ }
+
+ function onGenericChange(action)
+ {
+ if (action == "load")
+ {
+ isDirty = 0;
+ defaultMatcher.clear();
+ ElemHide.clear();
+ for (var _loopIndex20 = 0; _loopIndex20 < FilterStorage.subscriptions.length; ++_loopIndex20)
+ {
+ var subscription = FilterStorage.subscriptions[_loopIndex20];
+ if (!subscription.disabled)
+ {
+ subscription.filters.forEach(addFilter);
+ }
+ }
+ flushElemHide();
+ }
+ else if (action == "save")
+ {
+ isDirty = 0;
+ }
+ }
+ return exports;
+})();
+require.scopes["synchronizer"] = (function()
+{
+ var exports = {};
+ var Utils = require("utils").Utils;
+ var FilterStorage = require("filterStorage").FilterStorage;
+ var FilterNotifier = require("filterNotifier").FilterNotifier;
+ var Prefs = require("prefs").Prefs;
+ var _tempVar21 = require("filterClasses");
+ var Filter = _tempVar21.Filter;
+ var CommentFilter = _tempVar21.CommentFilter;
+ var _tempVar22 = require("subscriptionClasses");
+ var Subscription = _tempVar22.Subscription;
+ var DownloadableSubscription = _tempVar22.DownloadableSubscription;
+ var MILLISECONDS_IN_SECOND = 1000;
+ var SECONDS_IN_MINUTE = 60;
+ var SECONDS_IN_HOUR = 60 * SECONDS_IN_MINUTE;
+ var SECONDS_IN_DAY = 24 * SECONDS_IN_HOUR;
+ var INITIAL_DELAY = 6 * SECONDS_IN_MINUTE;
+ var CHECK_INTERVAL = SECONDS_IN_HOUR;
+ var MIN_EXPIRATION_INTERVAL = 1 * SECONDS_IN_DAY;
+ var MAX_EXPIRATION_INTERVAL = 14 * SECONDS_IN_DAY;
+ var MAX_ABSENSE_INTERVAL = 1 * SECONDS_IN_DAY;
+ var timer = null;
+ var executing =
+ {
+ __proto__: null
+ };
+ var Synchronizer = exports.Synchronizer =
+ {
+ init: function()
+ {
+ var callback = function()
+ {
+ timer.delay = CHECK_INTERVAL * MILLISECONDS_IN_SECOND;
+ checkSubscriptions();
+ };
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(callback, INITIAL_DELAY * MILLISECONDS_IN_SECOND, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ onShutdown.add(function()
+ {
+ timer.cancel();
+ });
+ },
+ isExecuting: function(url)
+ {
+ return url in executing;
+ },
+ execute: function(subscription, manual, forceDownload)
+ {
+ Utils.runAsync(this.executeInternal, this, subscription, manual, forceDownload);
+ },
+ executeInternal: function(subscription, manual, forceDownload)
+ {
+ var url = subscription.url;
+ if (url in executing)
+ {
+ return;
+ }
+ var newURL = subscription.nextURL;
+ var hadTemporaryRedirect = false;
+ subscription.nextURL = null;
+ var loadFrom = newURL;
+ var isBaseLocation = true;
+ if (!loadFrom)
+ {
+ loadFrom = url;
+ }
+ if (loadFrom == url)
+ {
+ if (subscription.alternativeLocations)
+ {
+ var options = [
+ [1, url]
+ ];
+ var totalWeight = 1;
+ for (var _loopIndex23 = 0; _loopIndex23 < subscription.alternativeLocations.split(",").length; ++_loopIndex23)
+ {
+ var alternative = subscription.alternativeLocations.split(",")[_loopIndex23];
+ if (!/^https?:\/\//.test(alternative))
+ {
+ continue;
+ }
+ var weight = 1;
+ var match = /;q=([\d\.]+)$/.exec(alternative);
+ if (match)
+ {
+ weight = parseFloat(match[1]);
+ if (isNaN(weight) || !isFinite(weight) || weight < 0)
+ {
+ weight = 1;
+ }
+ if (weight > 10)
+ {
+ weight = 10;
+ }
+ alternative = alternative.substr(0, match.index);
+ }
+ options.push([weight, alternative]);
+ totalWeight += weight;
+ }
+ var choice = Math.random() * totalWeight;
+ for (var _loopIndex24 = 0; _loopIndex24 < options.length; ++_loopIndex24)
+ {
+ var _tempVar25 = options[_loopIndex24];
+ var weight = _tempVar25[0];
+ var alternative = _tempVar25[1];
+ choice -= weight;
+ if (choice < 0)
+ {
+ loadFrom = alternative;
+ break;
+ }
+ }
+ isBaseLocation = loadFrom == url;
+ }
+ }
+ else
+ {
+ forceDownload = true;
+ }
+ var addonVersion = require("info").addonVersion;
+ loadFrom = loadFrom.replace(/%VERSION%/, "ABP" + addonVersion);
+ var request = null;
+
+ function errorCallback(error)
+ {
+ var channelStatus = -1;
+ try
+ {
+ channelStatus = request.channel.status;
+ }
+ catch (e){}
+ var responseStatus = "";
+ try
+ {
+ responseStatus = request.channel.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ }
+ catch (e){}
+ setError(subscription, error, channelStatus, responseStatus, loadFrom, isBaseLocation, manual);
+ }
+ try
+ {
+ request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+ request.mozBackgroundRequest = true;
+ request.open("GET", loadFrom);
+ }
+ catch (e)
+ {
+ errorCallback("synchronize_invalid_url");
+ return;
+ }
+ try
+ {
+ request.overrideMimeType("text/plain");
+ request.channel.loadFlags = request.channel.loadFlags | request.channel.INHIBIT_CACHING | request.channel.VALIDATE_ALWAYS;
+ if (request.channel instanceof Ci.nsIHttpChannel)
+ {
+ request.channel.redirectionLimit = 5;
+ }
+ var oldNotifications = request.channel.notificationCallbacks;
+ var oldEventSink = null;
+ request.channel.notificationCallbacks =
+ {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSink]),
+ getInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIChannelEventSink))
+ {
+ try
+ {
+ oldEventSink = oldNotifications.QueryInterface(iid);
+ }
+ catch (e){}
+ return this;
+ }
+ if (oldNotifications)
+ {
+ return oldNotifications.QueryInterface(iid);
+ }
+ else
+ {
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ },
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
+ {
+ if (isBaseLocation && !hadTemporaryRedirect && oldChannel instanceof Ci.nsIHttpChannel)
+ {
+ try
+ {
+ subscription.alternativeLocations = oldChannel.getResponseHeader("X-Alternative-Locations");
+ }
+ catch (e)
+ {
+ subscription.alternativeLocations = null;
+ }
+ }
+ if (flags & Ci.nsIChannelEventSink.REDIRECT_TEMPORARY)
+ {
+ hadTemporaryRedirect = true;
+ }
+ else if (!hadTemporaryRedirect)
+ {
+ newURL = newChannel.URI.spec;
+ }
+ if (oldEventSink)
+ {
+ oldEventSink.asyncOnChannelRedirect(oldChannel, newChannel, flags, callback);
+ }
+ else
+ {
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+ }
+ };
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+ if (subscription.lastModified && !forceDownload)
+ {
+ request.setRequestHeader("If-Modified-Since", subscription.lastModified);
+ }
+ request.addEventListener("error", function(ev)
+ {
+ if (onShutdown.done)
+ {
+ return;
+ }
+ delete executing[url];
+ try
+ {
+ request.channel.notificationCallbacks = null;
+ }
+ catch (e){}
+ errorCallback("synchronize_connection_error");
+ }, false);
+ request.addEventListener("load", function(ev)
+ {
+ if (onShutdown.done)
+ {
+ return;
+ }
+ delete executing[url];
+ try
+ {
+ request.channel.notificationCallbacks = null;
+ }
+ catch (e){}
+ if (request.status && request.status != 200 && request.status != 304)
+ {
+ errorCallback("synchronize_connection_error");
+ return;
+ }
+ var newFilters = null;
+ if (request.status != 304)
+ {
+ newFilters = readFilters(subscription, request.responseText, errorCallback);
+ if (!newFilters)
+ {
+ return;
+ }
+ subscription.lastModified = request.getResponseHeader("Last-Modified");
+ }
+ if (isBaseLocation && !hadTemporaryRedirect)
+ {
+ subscription.alternativeLocations = request.getResponseHeader("X-Alternative-Locations");
+ }
+ subscription.lastSuccess = subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
+ subscription.downloadStatus = "synchronize_ok";
+ subscription.errors = 0;
+ var now = Math.round(((new Date(request.getResponseHeader("Date"))).getTime() || Date.now()) / MILLISECONDS_IN_SECOND);
+ var expires = Math.round((new Date(request.getResponseHeader("Expires"))).getTime() / MILLISECONDS_IN_SECOND) || 0;
+ var expirationInterval = expires ? expires - now : 0;
+ for (var _loopIndex26 = 0; _loopIndex26 < (newFilters || subscription.filters).length; ++_loopIndex26)
+ {
+ var filter = (newFilters || subscription.filters)[_loopIndex26];
+ if (!(filter instanceof CommentFilter))
+ {
+ continue;
+ }
+ var match = /\bExpires\s*(?::|after)\s*(\d+)\s*(h)?/i.exec(filter.text);
+ if (match)
+ {
+ var interval = parseInt(match[1], 10);
+ if (match[2])
+ {
+ interval *= SECONDS_IN_HOUR;
+ }
+ else
+ {
+ interval *= SECONDS_IN_DAY;
+ }
+ if (interval > expirationInterval)
+ {
+ expirationInterval = interval;
+ }
+ }
+ }
+ expirationInterval = Math.min(Math.max(expirationInterval, MIN_EXPIRATION_INTERVAL), MAX_EXPIRATION_INTERVAL);
+ subscription.expires = subscription.lastDownload + expirationInterval * 2;
+ subscription.softExpiration = subscription.lastDownload + Math.round(expirationInterval * (Math.random() * 0.4 + 0.8));
+ if (newFilters)
+ {
+ var fixedTitle = false;
+ for (var i = 0; i < newFilters.length; i++)
+ {
+ var filter = newFilters[i];
+ if (!(filter instanceof CommentFilter))
+ {
+ continue;
+ }
+ var match = /^!\s*(\w+)\s*:\s*(.*)/.exec(filter.text);
+ if (match)
+ {
+ var keyword = match[1].toLowerCase();
+ var value = match[2];
+ var known = true;
+ if (keyword == "redirect")
+ {
+ if (isBaseLocation && value != url)
+ {
+ subscription.nextURL = value;
+ }
+ }
+ else if (keyword == "homepage")
+ {
+ var uri = Utils.makeURI(value);
+ if (uri && (uri.scheme == "http" || uri.scheme == "https"))
+ {
+ subscription.homepage = uri.spec;
+ }
+ }
+ else if (keyword == "title")
+ {
+ if (value)
+ {
+ subscription.title = value;
+ fixedTitle = true;
+ }
+ }
+ else
+ {
+ known = false;
+ }
+ if (known)
+ {
+ newFilters.splice(i--, 1);
+ }
+ }
+ }
+ subscription.fixedTitle = fixedTitle;
+ }
+ if (isBaseLocation && newURL && newURL != url)
+ {
+ var listed = subscription.url in FilterStorage.knownSubscriptions;
+ if (listed)
+ {
+ FilterStorage.removeSubscription(subscription);
+ }
+ url = newURL;
+ var newSubscription = Subscription.fromURL(url);
+ for (var key in newSubscription)
+ {
+ delete newSubscription[key];
+ }
+ for (var key in subscription)
+ {
+ newSubscription[key] = subscription[key];
+ }
+ delete Subscription.knownSubscriptions[subscription.url];
+ newSubscription.oldSubscription = subscription;
+ subscription = newSubscription;
+ subscription.url = url;
+ if (!(subscription.url in FilterStorage.knownSubscriptions) && listed)
+ {
+ FilterStorage.addSubscription(subscription);
+ }
+ }
+ if (newFilters)
+ {
+ FilterStorage.updateSubscriptionFilters(subscription, newFilters);
+ }
+ delete subscription.oldSubscription;
+ }, false);
+ executing[url] = true;
+ FilterNotifier.triggerListeners("subscription.downloadStatus", subscription);
+ try
+ {
+ request.send(null);
+ }
+ catch (e)
+ {
+ delete executing[url];
+ errorCallback("synchronize_connection_error");
+ return;
+ }
+ }
+ };
+ Synchronizer.init();
+
+ function checkSubscriptions()
+ {
+ if (!Prefs.subscriptions_autoupdate)
+ {
+ return;
+ }
+ var time = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
+ for (var _loopIndex27 = 0; _loopIndex27 < FilterStorage.subscriptions.length; ++_loopIndex27)
+ {
+ var subscription = FilterStorage.subscriptions[_loopIndex27];
+ if (!(subscription instanceof DownloadableSubscription))
+ {
+ continue;
+ }
+ if (subscription.lastCheck && time - subscription.lastCheck > MAX_ABSENSE_INTERVAL)
+ {
+ subscription.softExpiration += time - subscription.lastCheck;
+ }
+ subscription.lastCheck = time;
+ if (subscription.expires - time > MAX_EXPIRATION_INTERVAL)
+ {
+ subscription.expires = time + MAX_EXPIRATION_INTERVAL;
+ }
+ if (subscription.softExpiration - time > MAX_EXPIRATION_INTERVAL)
+ {
+ subscription.softExpiration = time + MAX_EXPIRATION_INTERVAL;
+ }
+ if (subscription.softExpiration > time && subscription.expires > time)
+ {
+ continue;
+ }
+ if (time - subscription.lastDownload >= MIN_EXPIRATION_INTERVAL)
+ {
+ Synchronizer.execute(subscription, false);
+ }
+ }
+ }
+
+ function readFilters(subscription, text, errorCallback)
+ {
+ var lines = text.split(/[\r\n]+/);
+ var match = /\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]/i.exec(lines[0]);
+ if (!match)
+ {
+ errorCallback("synchronize_invalid_data");
+ return null;
+ }
+ var minVersion = match[1];
+ for (var i = 0; i < lines.length; i++)
+ {
+ var match = /!\s*checksum[\s\-:]+([\w\+\/]+)/i.exec(lines[i]);
+ if (match)
+ {
+ lines.splice(i, 1);
+ var checksum = Utils.generateChecksum(lines);
+ if (checksum && checksum != match[1])
+ {
+ errorCallback("synchronize_checksum_mismatch");
+ return null;
+ }
+ break;
+ }
+ }
+ delete subscription.requiredVersion;
+ delete subscription.upgradeRequired;
+ if (minVersion)
+ {
+ var addonVersion = require("info").addonVersion;
+ subscription.requiredVersion = minVersion;
+ if (Services.vc.compare(minVersion, addonVersion) > 0)
+ {
+ subscription.upgradeRequired = true;
+ }
+ }
+ lines.shift();
+ var result = [];
+ for (var _loopIndex28 = 0; _loopIndex28 < lines.length; ++_loopIndex28)
+ {
+ var line = lines[_loopIndex28];
+ line = Filter.normalize(line);
+ if (line)
+ {
+ result.push(Filter.fromText(line));
+ }
+ }
+ return result;
+ }
+
+ function setError(subscription, error, channelStatus, responseStatus, downloadURL, isBaseLocation, manual)
+ {
+ if (!isBaseLocation)
+ {
+ subscription.alternativeLocations = null;
+ }
+ try
+ {
+ Cu.reportError("Adblock Plus: Downloading filter subscription " + subscription.title + " failed (" + Utils.getString(error) + ")\n" + "Download address: " + downloadURL + "\n" + "Channel status: " + channelStatus + "\n" + "Server response: " + responseStatus);
+ }
+ catch (e){}
+ subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
+ subscription.downloadStatus = error;
+ if (!manual)
+ {
+ if (error == "synchronize_checksum_mismatch")
+ {
+ subscription.errors = 0;
+ }
+ else
+ {
+ subscription.errors++;
+ }
+ if (subscription.errors >= Prefs.subscriptions_fallbackerrors && /^https?:\/\//i.test(subscription.url))
+ {
+ subscription.errors = 0;
+ var fallbackURL = Prefs.subscriptions_fallbackurl;
+ var addonVersion = require("info").addonVersion;
+ fallbackURL = fallbackURL.replace(/%VERSION%/g, encodeURIComponent(addonVersion));
+ fallbackURL = fallbackURL.replace(/%SUBSCRIPTION%/g, encodeURIComponent(subscription.url));
+ fallbackURL = fallbackURL.replace(/%URL%/g, encodeURIComponent(downloadURL));
+ fallbackURL = fallbackURL.replace(/%ERROR%/g, encodeURIComponent(error));
+ fallbackURL = fallbackURL.replace(/%CHANNELSTATUS%/g, encodeURIComponent(channelStatus));
+ fallbackURL = fallbackURL.replace(/%RESPONSESTATUS%/g, encodeURIComponent(responseStatus));
+ var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+ request.mozBackgroundRequest = true;
+ request.open("GET", fallbackURL);
+ request.overrideMimeType("text/plain");
+ request.channel.loadFlags = request.channel.loadFlags | request.channel.INHIBIT_CACHING | request.channel.VALIDATE_ALWAYS;
+ request.addEventListener("load", function(ev)
+ {
+ if (onShutdown.done)
+ {
+ return;
+ }
+ if (!(subscription.url in FilterStorage.knownSubscriptions))
+ {
+ return;
+ }
+ var match = /^(\d+)(?:\s+(\S+))?$/.exec(request.responseText);
+ if (match && match[1] == "301" && match[2])
+ {
+ subscription.nextURL = match[2];
+ }
+ else if (match && match[1] == "410")
+ {
+ var data = "[Adblock]\n" + subscription.filters.map(function(f)
+ {
+ return f.text;
+ }).join("\n");
+ var url = "data:text/plain," + encodeURIComponent(data);
+ var newSubscription = Subscription.fromURL(url);
+ newSubscription.title = subscription.title;
+ newSubscription.disabled = subscription.disabled;
+ FilterStorage.removeSubscription(subscription);
+ FilterStorage.addSubscription(newSubscription);
+ Synchronizer.execute(newSubscription);
+ }
+ }, false);
+ request.send(null);
+ }
+ }
+ }
+ return exports;
+})();
+
+var publicSuffixes = {
+ "0.bg": 1,
+ "1.bg": 1,
+ "2.bg": 1,
+ "2000.hu": 1,
+ "3.bg": 1,
+ "4.bg": 1,
+ "5.bg": 1,
+ "6.bg": 1,
+ "6bone.pl": 1,
+ "7.bg": 1,
+ "8.bg": 1,
+ "9.bg": 1,
+ "a.bg": 1,
+ "a.se": 1,
+ "aa.no": 1,
+ "aarborte.no": 1,
+ "ab.ca": 1,
+ "abashiri.hokkaido.jp": 1,
+ "abeno.osaka.jp": 1,
+ "abiko.chiba.jp": 1,
+ "abira.hokkaido.jp": 1,
+ "abo.pa": 1,
+ "abu.yamaguchi.jp": 1,
+ "ac.ae": 1,
+ "ac.at": 1,
+ "ac.be": 1,
+ "ac.ci": 1,
+ "ac.cn": 1,
+ "ac.cr": 1,
+ "ac.gn": 1,
+ "ac.id": 1,
+ "ac.im": 1,
+ "ac.in": 1,
+ "ac.ir": 1,
+ "ac.jp": 1,
+ "ac.kr": 1,
+ "ac.ma": 1,
+ "ac.me": 1,
+ "ac.mu": 1,
+ "ac.mw": 1,
+ "ac.ng": 1,
+ "ac.pa": 1,
+ "ac.pr": 1,
+ "ac.rs": 1,
+ "ac.ru": 1,
+ "ac.rw": 1,
+ "ac.se": 1,
+ "ac.sz": 1,
+ "ac.th": 1,
+ "ac.tj": 1,
+ "ac.tz": 1,
+ "ac.ug": 1,
+ "ac.vn": 1,
+ "aca.pro": 1,
+ "academy.museum": 1,
+ "accident-investigation.aero": 1,
+ "accident-prevention.aero": 1,
+ "achi.nagano.jp": 1,
+ "act.au": 1,
+ "act.edu.au": 1,
+ "act.gov.au": 1,
+ "ad.jp": 1,
+ "adachi.tokyo.jp": 1,
+ "adm.br": 1,
+ "adult.ht": 1,
+ "adv.br": 1,
+ "adygeya.ru": 1,
+ "ae.org": 1,
+ "aejrie.no": 1,
+ "aero.mv": 1,
+ "aero.tt": 1,
+ "aerobatic.aero": 1,
+ "aeroclub.aero": 1,
+ "aerodrome.aero": 1,
+ "aeroport.fr": 1,
+ "afjord.no": 1,
+ "ag.it": 1,
+ "aga.niigata.jp": 1,
+ "agano.niigata.jp": 1,
+ "agdenes.no": 1,
+ "agematsu.nagano.jp": 1,
+ "agents.aero": 1,
+ "agr.br": 1,
+ "agrar.hu": 1,
+ "agriculture.museum": 1,
+ "agrigento.it": 1,
+ "agrinet.tn": 1,
+ "agro.pl": 1,
+ "aguni.okinawa.jp": 1,
+ "ah.cn": 1,
+ "ah.no": 1,
+ "aibetsu.hokkaido.jp": 1,
+ "aichi.jp": 1,
+ "aid.pl": 1,
+ "aikawa.kanagawa.jp": 1,
+ "ainan.ehime.jp": 1,
+ "aioi.hyogo.jp": 1,
+ "aip.ee": 1,
+ "air-surveillance.aero": 1,
+ "air-traffic-control.aero": 1,
+ "air.museum": 1,
+ "aircraft.aero": 1,
+ "airguard.museum": 1,
+ "airline.aero": 1,
+ "airport.aero": 1,
+ "airtraffic.aero": 1,
+ "aisai.aichi.jp": 1,
+ "aisho.shiga.jp": 1,
+ "aizubange.fukushima.jp": 1,
+ "aizumi.tokushima.jp": 1,
+ "aizumisato.fukushima.jp": 1,
+ "aizuwakamatsu.fukushima.jp": 1,
+ "ak.us": 1,
+ "akabira.hokkaido.jp": 1,
+ "akagi.shimane.jp": 1,
+ "akaiwa.okayama.jp": 1,
+ "akashi.hyogo.jp": 1,
+ "aki.kochi.jp": 1,
+ "akiruno.tokyo.jp": 1,
+ "akishima.tokyo.jp": 1,
+ "akita.akita.jp": 1,
+ "akita.jp": 1,
+ "akkeshi.hokkaido.jp": 1,
+ "aknoluokta.no": 1,
+ "ako.hyogo.jp": 1,
+ "akrehamn.no": 1,
+ "akune.kagoshima.jp": 1,
+ "al.it": 1,
+ "al.no": 1,
+ "al.us": 1,
+ "alabama.museum": 1,
+ "alaheadju.no": 1,
+ "aland.fi": 1,
+ "alaska.museum": 1,
+ "alessandria.it": 1,
+ "alesund.no": 1,
+ "algard.no": 1,
+ "alstahaug.no": 1,
+ "alta.no": 1,
+ "altai.ru": 1,
+ "alto-adige.it": 1,
+ "altoadige.it": 1,
+ "alvdal.no": 1,
+ "am.br": 1,
+ "ama.aichi.jp": 1,
+ "ama.shimane.jp": 1,
+ "amagasaki.hyogo.jp": 1,
+ "amakusa.kumamoto.jp": 1,
+ "amami.kagoshima.jp": 1,
+ "amber.museum": 1,
+ "ambulance.aero": 1,
+ "ambulance.museum": 1,
+ "american.museum": 1,
+ "americana.museum": 1,
+ "americanantiques.museum": 1,
+ "americanart.museum": 1,
+ "ami.ibaraki.jp": 1,
+ "amli.no": 1,
+ "amot.no": 1,
+ "amsterdam.museum": 1,
+ "amur.ru": 1,
+ "amursk.ru": 1,
+ "amusement.aero": 1,
+ "an.it": 1,
+ "anamizu.ishikawa.jp": 1,
+ "anan.nagano.jp": 1,
+ "anan.tokushima.jp": 1,
+ "ancona.it": 1,
+ "and.museum": 1,
+ "andasuolo.no": 1,
+ "andebu.no": 1,
+ "ando.nara.jp": 1,
+ "andoy.no": 1,
+ "andria-barletta-trani.it": 1,
+ "andria-trani-barletta.it": 1,
+ "andriabarlettatrani.it": 1,
+ "andriatranibarletta.it": 1,
+ "and\u00f8y.no": 1,
+ "anjo.aichi.jp": 1,
+ "annaka.gunma.jp": 1,
+ "annefrank.museum": 1,
+ "anpachi.gifu.jp": 1,
+ "anthro.museum": 1,
+ "anthropology.museum": 1,
+ "antiques.museum": 1,
+ "ao.it": 1,
+ "aogaki.hyogo.jp": 1,
+ "aogashima.tokyo.jp": 1,
+ "aoki.nagano.jp": 1,
+ "aomori.aomori.jp": 1,
+ "aomori.jp": 1,
+ "aosta.it": 1,
+ "aoste.it": 1,
+ "ap.it": 1,
+ "appspot.com": 1,
+ "aq.it": 1,
+ "aquarium.museum": 1,
+ "aquila.it": 1,
+ "ar": 2,
+ "ar.com": 1,
+ "ar.it": 1,
+ "ar.us": 1,
+ "arai.shizuoka.jp": 1,
+ "arakawa.saitama.jp": 1,
+ "arakawa.tokyo.jp": 1,
+ "arao.kumamoto.jp": 1,
+ "arboretum.museum": 1,
+ "archaeological.museum": 1,
+ "archaeology.museum": 1,
+ "architecture.museum": 1,
+ "ardal.no": 1,
+ "aremark.no": 1,
+ "arendal.no": 1,
+ "arezzo.it": 1,
+ "ariake.saga.jp": 1,
+ "arida.wakayama.jp": 1,
+ "aridagawa.wakayama.jp": 1,
+ "arita.saga.jp": 1,
+ "arkhangelsk.ru": 1,
+ "arna.no": 1,
+ "arq.br": 1,
+ "art.br": 1,
+ "art.do": 1,
+ "art.dz": 1,
+ "art.ht": 1,
+ "art.museum": 1,
+ "art.pl": 1,
+ "art.sn": 1,
+ "artanddesign.museum": 1,
+ "artcenter.museum": 1,
+ "artdeco.museum": 1,
+ "arteducation.museum": 1,
+ "artgallery.museum": 1,
+ "arts.co": 1,
+ "arts.museum": 1,
+ "arts.nf": 1,
+ "arts.ro": 1,
+ "artsandcrafts.museum": 1,
+ "as.us": 1,
+ "asago.hyogo.jp": 1,
+ "asahi.chiba.jp": 1,
+ "asahi.ibaraki.jp": 1,
+ "asahi.mie.jp": 1,
+ "asahi.nagano.jp": 1,
+ "asahi.toyama.jp": 1,
+ "asahi.yamagata.jp": 1,
+ "asahikawa.hokkaido.jp": 1,
+ "asaka.saitama.jp": 1,
+ "asakawa.fukushima.jp": 1,
+ "asakuchi.okayama.jp": 1,
+ "asaminami.hiroshima.jp": 1,
+ "ascoli-piceno.it": 1,
+ "ascolipiceno.it": 1,
+ "aseral.no": 1,
+ "ashibetsu.hokkaido.jp": 1,
+ "ashikaga.tochigi.jp": 1,
+ "ashiya.fukuoka.jp": 1,
+ "ashiya.hyogo.jp": 1,
+ "ashoro.hokkaido.jp": 1,
+ "asker.no": 1,
+ "askim.no": 1,
+ "askoy.no": 1,
+ "askvoll.no": 1,
+ "ask\u00f8y.no": 1,
+ "asmatart.museum": 1,
+ "asn.au": 1,
+ "asn.lv": 1,
+ "asnes.no": 1,
+ "aso.kumamoto.jp": 1,
+ "ass.km": 1,
+ "assabu.hokkaido.jp": 1,
+ "assassination.museum": 1,
+ "assedic.fr": 1,
+ "assisi.museum": 1,
+ "assn.lk": 1,
+ "asso.bj": 1,
+ "asso.ci": 1,
+ "asso.dz": 1,
+ "asso.fr": 1,
+ "asso.gp": 1,
+ "asso.ht": 1,
+ "asso.km": 1,
+ "asso.mc": 1,
+ "asso.nc": 1,
+ "asso.re": 1,
+ "association.aero": 1,
+ "association.museum": 1,
+ "asti.it": 1,
+ "astrakhan.ru": 1,
+ "astronomy.museum": 1,
+ "asuke.aichi.jp": 1,
+ "at-band-camp.net": 1,
+ "at.it": 1,
+ "atami.shizuoka.jp": 1,
+ "ath.cx": 1,
+ "atlanta.museum": 1,
+ "atm.pl": 1,
+ "ato.br": 1,
+ "atsugi.kanagawa.jp": 1,
+ "atsuma.hokkaido.jp": 1,
+ "audnedaln.no": 1,
+ "augustow.pl": 1,
+ "aukra.no": 1,
+ "aure.no": 1,
+ "aurland.no": 1,
+ "aurskog-holand.no": 1,
+ "aurskog-h\u00f8land.no": 1,
+ "austevoll.no": 1,
+ "austin.museum": 1,
+ "australia.museum": 1,
+ "austrheim.no": 1,
+ "author.aero": 1,
+ "auto.pl": 1,
+ "automotive.museum": 1,
+ "av.it": 1,
+ "avellino.it": 1,
+ "averoy.no": 1,
+ "aver\u00f8y.no": 1,
+ "aviation.museum": 1,
+ "avocat.fr": 1,
+ "avoues.fr": 1,
+ "awaji.hyogo.jp": 1,
+ "axis.museum": 1,
+ "aya.miyazaki.jp": 1,
+ "ayabe.kyoto.jp": 1,
+ "ayagawa.kagawa.jp": 1,
+ "ayase.kanagawa.jp": 1,
+ "az.us": 1,
+ "azumino.nagano.jp": 1,
+ "a\u00e9roport.ci": 1,
+ "b.bg": 1,
+ "b.br": 1,
+ "b.se": 1,
+ "ba.it": 1,
+ "babia-gora.pl": 1,
+ "badaddja.no": 1,
+ "badajoz.museum": 1,
+ "baghdad.museum": 1,
+ "bahcavuotna.no": 1,
+ "bahccavuotna.no": 1,
+ "bahn.museum": 1,
+ "baidar.no": 1,
+ "baikal.ru": 1,
+ "bajddar.no": 1,
+ "balat.no": 1,
+ "bale.museum": 1,
+ "balestrand.no": 1,
+ "ballangen.no": 1,
+ "ballooning.aero": 1,
+ "balsan.it": 1,
+ "balsfjord.no": 1,
+ "baltimore.museum": 1,
+ "bamble.no": 1,
+ "bandai.fukushima.jp": 1,
+ "bando.ibaraki.jp": 1,
+ "bar.pro": 1,
+ "barcelona.museum": 1,
+ "bardu.no": 1,
+ "bari.it": 1,
+ "barletta-trani-andria.it": 1,
+ "barlettatraniandria.it": 1,
+ "barreau.bj": 1,
+ "barrel-of-knowledge.info": 1,
+ "barrell-of-knowledge.info": 1,
+ "barum.no": 1,
+ "baseball.museum": 1,
+ "basel.museum": 1,
+ "bashkiria.ru": 1,
+ "baths.museum": 1,
+ "bato.tochigi.jp": 1,
+ "batsfjord.no": 1,
+ "bauern.museum": 1,
+ "bc.ca": 1,
+ "bd": 2,
+ "bd.se": 1,
+ "bearalvahki.no": 1,
+ "bearalv\u00e1hki.no": 1,
+ "beardu.no": 1,
+ "beauxarts.museum": 1,
+ "bedzin.pl": 1,
+ "beeldengeluid.museum": 1,
+ "beiarn.no": 1,
+ "belau.pw": 1,
+ "belgorod.ru": 1,
+ "bellevue.museum": 1,
+ "belluno.it": 1,
+ "benevento.it": 1,
+ "beppu.oita.jp": 1,
+ "berg.no": 1,
+ "bergamo.it": 1,
+ "bergbau.museum": 1,
+ "bergen.no": 1,
+ "berkeley.museum": 1,
+ "berlevag.no": 1,
+ "berlev\u00e5g.no": 1,
+ "berlin.museum": 1,
+ "bern.museum": 1,
+ "beskidy.pl": 1,
+ "better-than.tv": 1,
+ "bg.it": 1,
+ "bi.it": 1,
+ "bialowieza.pl": 1,
+ "bialystok.pl": 1,
+ "bibai.hokkaido.jp": 1,
+ "bible.museum": 1,
+ "biei.hokkaido.jp": 1,
+ "bielawa.pl": 1,
+ "biella.it": 1,
+ "bieszczady.pl": 1,
+ "bievat.no": 1,
+ "biev\u00e1t.no": 1,
+ "bifuka.hokkaido.jp": 1,
+ "bihoro.hokkaido.jp": 1,
+ "bilbao.museum": 1,
+ "bill.museum": 1,
+ "bindal.no": 1,
+ "bio.br": 1,
+ "bir.ru": 1,
+ "biratori.hokkaido.jp": 1,
+ "birdart.museum": 1,
+ "birkenes.no": 1,
+ "birthplace.museum": 1,
+ "biz.at": 1,
+ "biz.az": 1,
+ "biz.bb": 1,
+ "biz.ki": 1,
+ "biz.mv": 1,
+ "biz.mw": 1,
+ "biz.nr": 1,
+ "biz.pk": 1,
+ "biz.pl": 1,
+ "biz.pr": 1,
+ "biz.tj": 1,
+ "biz.tt": 1,
+ "biz.vn": 1,
+ "bizen.okayama.jp": 1,
+ "bj.cn": 1,
+ "bjarkoy.no": 1,
+ "bjark\u00f8y.no": 1,
+ "bjerkreim.no": 1,
+ "bjugn.no": 1,
+ "bl.it": 1,
+ "bl.uk": 0,
+ "blog.br": 1,
+ "blogdns.com": 1,
+ "blogdns.net": 1,
+ "blogdns.org": 1,
+ "blogsite.org": 1,
+ "bmd.br": 1,
+ "bn": 2,
+ "bn.it": 1,
+ "bo.it": 1,
+ "bo.nordland.no": 1,
+ "bo.telemark.no": 1,
+ "bodo.no": 1,
+ "bod\u00f8.no": 1,
+ "bokn.no": 1,
+ "boldlygoingnowhere.org": 1,
+ "boleslawiec.pl": 1,
+ "bologna.it": 1,
+ "bolt.hu": 1,
+ "bolzano.it": 1,
+ "bomlo.no": 1,
+ "bonn.museum": 1,
+ "boston.museum": 1,
+ "botanical.museum": 1,
+ "botanicalgarden.museum": 1,
+ "botanicgarden.museum": 1,
+ "botany.museum": 1,
+ "bozen.it": 1,
+ "br.com": 1,
+ "br.it": 1,
+ "brand.se": 1,
+ "brandywinevalley.museum": 1,
+ "brasil.museum": 1,
+ "bremanger.no": 1,
+ "brescia.it": 1,
+ "brindisi.it": 1,
+ "bristol.museum": 1,
+ "british-library.uk": 0,
+ "british.museum": 1,
+ "britishcolumbia.museum": 1,
+ "broadcast.museum": 1,
+ "broke-it.net": 1,
+ "broker.aero": 1,
+ "bronnoy.no": 1,
+ "bronnoysund.no": 1,
+ "brumunddal.no": 1,
+ "brunel.museum": 1,
+ "brussel.museum": 1,
+ "brussels.museum": 1,
+ "bruxelles.museum": 1,
+ "bryansk.ru": 1,
+ "bryne.no": 1,
+ "br\u00f8nn\u00f8y.no": 1,
+ "br\u00f8nn\u00f8ysund.no": 1,
+ "bs.it": 1,
+ "bt.it": 1,
+ "bu.no": 1,
+ "budejju.no": 1,
+ "building.museum": 1,
+ "bungoono.oita.jp": 1,
+ "bungotakada.oita.jp": 1,
+ "bunkyo.tokyo.jp": 1,
+ "burghof.museum": 1,
+ "buryatia.ru": 1,
+ "bus.museum": 1,
+ "busan.kr": 1,
+ "bushey.museum": 1,
+ "buyshouses.net": 1,
+ "buzen.fukuoka.jp": 1,
+ "bv.nl": 1,
+ "bydgoszcz.pl": 1,
+ "bygland.no": 1,
+ "bykle.no": 1,
+ "bytom.pl": 1,
+ "bz.it": 1,
+ "b\u00e1hcavuotna.no": 1,
+ "b\u00e1hccavuotna.no": 1,
+ "b\u00e1id\u00e1r.no": 1,
+ "b\u00e1jddar.no": 1,
+ "b\u00e1l\u00e1t.no": 1,
+ "b\u00e5d\u00e5ddj\u00e5.no": 1,
+ "b\u00e5tsfjord.no": 1,
+ "b\u00e6rum.no": 1,
+ "b\u00f8.nordland.no": 1,
+ "b\u00f8.telemark.no": 1,
+ "b\u00f8mlo.no": 1,
+ "c.bg": 1,
+ "c.la": 1,
+ "c.se": 1,
+ "ca.it": 1,
+ "ca.na": 1,
+ "ca.us": 1,
+ "caa.aero": 1,
+ "cadaques.museum": 1,
+ "cagliari.it": 1,
+ "cahcesuolo.no": 1,
+ "california.museum": 1,
+ "caltanissetta.it": 1,
+ "cambridge.museum": 1,
+ "campidano-medio.it": 1,
+ "campidanomedio.it": 1,
+ "campobasso.it": 1,
+ "can.museum": 1,
+ "canada.museum": 1,
+ "capebreton.museum": 1,
+ "carbonia-iglesias.it": 1,
+ "carboniaiglesias.it": 1,
+ "cargo.aero": 1,
+ "carrara-massa.it": 1,
+ "carraramassa.it": 1,
+ "carrier.museum": 1,
+ "cartoonart.museum": 1,
+ "casadelamoneda.museum": 1,
+ "caserta.it": 1,
+ "casino.hu": 1,
+ "castle.museum": 1,
+ "castres.museum": 1,
+ "catania.it": 1,
+ "catanzaro.it": 1,
+ "catering.aero": 1,
+ "cb.it": 1,
+ "cbg.ru": 1,
+ "cc.ak.us": 1,
+ "cc.al.us": 1,
+ "cc.ar.us": 1,
+ "cc.as.us": 1,
+ "cc.az.us": 1,
+ "cc.ca.us": 1,
+ "cc.co.us": 1,
+ "cc.ct.us": 1,
+ "cc.dc.us": 1,
+ "cc.de.us": 1,
+ "cc.fl.us": 1,
+ "cc.ga.us": 1,
+ "cc.gu.us": 1,
+ "cc.hi.us": 1,
+ "cc.ia.us": 1,
+ "cc.id.us": 1,
+ "cc.il.us": 1,
+ "cc.in.us": 1,
+ "cc.ks.us": 1,
+ "cc.ky.us": 1,
+ "cc.la.us": 1,
+ "cc.ma.us": 1,
+ "cc.md.us": 1,
+ "cc.me.us": 1,
+ "cc.mi.us": 1,
+ "cc.mn.us": 1,
+ "cc.mo.us": 1,
+ "cc.ms.us": 1,
+ "cc.mt.us": 1,
+ "cc.na": 1,
+ "cc.nc.us": 1,
+ "cc.nd.us": 1,
+ "cc.ne.us": 1,
+ "cc.nh.us": 1,
+ "cc.nj.us": 1,
+ "cc.nm.us": 1,
+ "cc.nv.us": 1,
+ "cc.ny.us": 1,
+ "cc.oh.us": 1,
+ "cc.ok.us": 1,
+ "cc.or.us": 1,
+ "cc.pa.us": 1,
+ "cc.pr.us": 1,
+ "cc.ri.us": 1,
+ "cc.sc.us": 1,
+ "cc.sd.us": 1,
+ "cc.tn.us": 1,
+ "cc.tx.us": 1,
+ "cc.ut.us": 1,
+ "cc.va.us": 1,
+ "cc.vi.us": 1,
+ "cc.vt.us": 1,
+ "cc.wa.us": 1,
+ "cc.wi.us": 1,
+ "cc.wv.us": 1,
+ "cc.wy.us": 1,
+ "cci.fr": 1,
+ "ce.it": 1,
+ "cechire.com": 1,
+ "celtic.museum": 1,
+ "center.museum": 1,
+ "certification.aero": 1,
+ "cesena-forli.it": 1,
+ "cesenaforli.it": 1,
+ "ch.it": 1,
+ "chambagri.fr": 1,
+ "championship.aero": 1,
+ "charter.aero": 1,
+ "chattanooga.museum": 1,
+ "chel.ru": 1,
+ "cheltenham.museum": 1,
+ "chelyabinsk.ru": 1,
+ "cherkassy.ua": 1,
+ "cherkasy.ua": 1,
+ "chernigov.ua": 1,
+ "chernihiv.ua": 1,
+ "chernivtsi.ua": 1,
+ "chernovtsy.ua": 1,
+ "chesapeakebay.museum": 1,
+ "chiba.jp": 1,
+ "chicago.museum": 1,
+ "chichibu.saitama.jp": 1,
+ "chieti.it": 1,
+ "chigasaki.kanagawa.jp": 1,
+ "chihayaakasaka.osaka.jp": 1,
+ "chijiwa.nagasaki.jp": 1,
+ "chikugo.fukuoka.jp": 1,
+ "chikuho.fukuoka.jp": 1,
+ "chikuhoku.nagano.jp": 1,
+ "chikujo.fukuoka.jp": 1,
+ "chikuma.nagano.jp": 1,
+ "chikusei.ibaraki.jp": 1,
+ "chikushino.fukuoka.jp": 1,
+ "chikuzen.fukuoka.jp": 1,
+ "children.museum": 1,
+ "childrens.museum": 1,
+ "childrensgarden.museum": 1,
+ "chino.nagano.jp": 1,
+ "chippubetsu.hokkaido.jp": 1,
+ "chiropractic.museum": 1,
+ "chirurgiens-dentistes.fr": 1,
+ "chiryu.aichi.jp": 1,
+ "chita.aichi.jp": 1,
+ "chita.ru": 1,
+ "chitose.hokkaido.jp": 1,
+ "chiyoda.gunma.jp": 1,
+ "chiyoda.tokyo.jp": 1,
+ "chizu.tottori.jp": 1,
+ "chocolate.museum": 1,
+ "chofu.tokyo.jp": 1,
+ "chonan.chiba.jp": 1,
+ "chosei.chiba.jp": 1,
+ "choshi.chiba.jp": 1,
+ "choyo.kumamoto.jp": 1,
+ "christiansburg.museum": 1,
+ "chtr.k12.ma.us": 1,
+ "chukotka.ru": 1,
+ "chungbuk.kr": 1,
+ "chungnam.kr": 1,
+ "chuo.chiba.jp": 1,
+ "chuo.fukuoka.jp": 1,
+ "chuo.osaka.jp": 1,
+ "chuo.tokyo.jp": 1,
+ "chuo.yamanashi.jp": 1,
+ "chuvashia.ru": 1,
+ "ci.it": 1,
+ "cieszyn.pl": 1,
+ "cim.br": 1,
+ "cincinnati.museum": 1,
+ "cinema.museum": 1,
+ "circus.museum": 1,
+ "city.hu": 1,
+ "city.kawasaki.jp": 0,
+ "city.kitakyushu.jp": 0,
+ "city.kobe.jp": 0,
+ "city.nagoya.jp": 0,
+ "city.sapporo.jp": 0,
+ "city.sendai.jp": 0,
+ "city.yokohama.jp": 0,
+ "civilaviation.aero": 1,
+ "civilisation.museum": 1,
+ "civilization.museum": 1,
+ "civilwar.museum": 1,
+ "ck": 2,
+ "ck.ua": 1,
+ "cl.it": 1,
+ "clinton.museum": 1,
+ "clock.museum": 1,
+ "club.aero": 1,
+ "club.tw": 1,
+ "cmw.ru": 1,
+ "cn.com": 1,
+ "cn.it": 1,
+ "cn.ua": 1,
+ "cng.br": 1,
+ "cnt.br": 1,
+ "co.ae": 1,
+ "co.ag": 1,
+ "co.ao": 1,
+ "co.at": 1,
+ "co.ba": 1,
+ "co.bi": 1,
+ "co.bw": 1,
+ "co.ca": 1,
+ "co.ci": 1,
+ "co.cl": 1,
+ "co.cr": 1,
+ "co.gg": 1,
+ "co.gy": 1,
+ "co.hu": 1,
+ "co.id": 1,
+ "co.im": 1,
+ "co.in": 1,
+ "co.ir": 1,
+ "co.it": 1,
+ "co.je": 1,
+ "co.jp": 1,
+ "co.kr": 1,
+ "co.lc": 1,
+ "co.ls": 1,
+ "co.ma": 1,
+ "co.me": 1,
+ "co.mu": 1,
+ "co.mw": 1,
+ "co.na": 1,
+ "co.nl": 1,
+ "co.no": 1,
+ "co.pl": 1,
+ "co.pn": 1,
+ "co.pw": 1,
+ "co.rs": 1,
+ "co.rw": 1,
+ "co.st": 1,
+ "co.sz": 1,
+ "co.th": 1,
+ "co.tj": 1,
+ "co.tm": 1,
+ "co.tt": 1,
+ "co.tz": 1,
+ "co.ua": 1,
+ "co.ug": 1,
+ "co.us": 1,
+ "co.uz": 1,
+ "co.ve": 1,
+ "co.vi": 1,
+ "coal.museum": 1,
+ "coastaldefence.museum": 1,
+ "cody.museum": 1,
+ "coldwar.museum": 1,
+ "collection.museum": 1,
+ "colonialwilliamsburg.museum": 1,
+ "coloradoplateau.museum": 1,
+ "columbia.museum": 1,
+ "columbus.museum": 1,
+ "com.ac": 1,
+ "com.af": 1,
+ "com.ag": 1,
+ "com.ai": 1,
+ "com.al": 1,
+ "com.an": 1,
+ "com.au": 1,
+ "com.aw": 1,
+ "com.az": 1,
+ "com.ba": 1,
+ "com.bb": 1,
+ "com.bh": 1,
+ "com.bi": 1,
+ "com.bm": 1,
+ "com.bo": 1,
+ "com.br": 1,
+ "com.bs": 1,
+ "com.bt": 1,
+ "com.by": 1,
+ "com.bz": 1,
+ "com.ci": 1,
+ "com.cn": 1,
+ "com.co": 1,
+ "com.cu": 1,
+ "com.de": 1,
+ "com.dm": 1,
+ "com.do": 1,
+ "com.dz": 1,
+ "com.ec": 1,
+ "com.ee": 1,
+ "com.eg": 1,
+ "com.es": 1,
+ "com.fr": 1,
+ "com.ge": 1,
+ "com.gh": 1,
+ "com.gi": 1,
+ "com.gn": 1,
+ "com.gp": 1,
+ "com.gr": 1,
+ "com.gy": 1,
+ "com.hk": 1,
+ "com.hn": 1,
+ "com.hr": 1,
+ "com.ht": 1,
+ "com.io": 1,
+ "com.iq": 1,
+ "com.is": 1,
+ "com.jo": 1,
+ "com.kg": 1,
+ "com.ki": 1,
+ "com.km": 1,
+ "com.kp": 1,
+ "com.ky": 1,
+ "com.kz": 1,
+ "com.la": 1,
+ "com.lb": 1,
+ "com.lc": 1,
+ "com.lk": 1,
+ "com.lr": 1,
+ "com.lv": 1,
+ "com.ly": 1,
+ "com.mg": 1,
+ "com.mk": 1,
+ "com.ml": 1,
+ "com.mo": 1,
+ "com.mu": 1,
+ "com.mv": 1,
+ "com.mw": 1,
+ "com.mx": 1,
+ "com.my": 1,
+ "com.na": 1,
+ "com.nf": 1,
+ "com.ng": 1,
+ "com.nr": 1,
+ "com.pa": 1,
+ "com.pe": 1,
+ "com.pf": 1,
+ "com.ph": 1,
+ "com.pk": 1,
+ "com.pl": 1,
+ "com.pr": 1,
+ "com.ps": 1,
+ "com.pt": 1,
+ "com.qa": 1,
+ "com.re": 1,
+ "com.ro": 1,
+ "com.ru": 1,
+ "com.rw": 1,
+ "com.sa": 1,
+ "com.sb": 1,
+ "com.sc": 1,
+ "com.sd": 1,
+ "com.sg": 1,
+ "com.sh": 1,
+ "com.sl": 1,
+ "com.sn": 1,
+ "com.so": 1,
+ "com.st": 1,
+ "com.sy": 1,
+ "com.tj": 1,
+ "com.tm": 1,
+ "com.tn": 1,
+ "com.to": 1,
+ "com.tt": 1,
+ "com.tw": 1,
+ "com.ua": 1,
+ "com.ug": 1,
+ "com.uy": 1,
+ "com.uz": 1,
+ "com.vc": 1,
+ "com.ve": 1,
+ "com.vi": 1,
+ "com.vn": 1,
+ "com.ws": 1,
+ "communication.museum": 1,
+ "communications.museum": 1,
+ "community.museum": 1,
+ "como.it": 1,
+ "computer.museum": 1,
+ "computerhistory.museum": 1,
+ "comunica\u00e7\u00f5es.museum": 1,
+ "conf.au": 1,
+ "conf.lv": 1,
+ "conference.aero": 1,
+ "congresodelalengua3.ar": 0,
+ "consulado.st": 1,
+ "consultant.aero": 1,
+ "consulting.aero": 1,
+ "contemporary.museum": 1,
+ "contemporaryart.museum": 1,
+ "control.aero": 1,
+ "convent.museum": 1,
+ "coop.br": 1,
+ "coop.ht": 1,
+ "coop.km": 1,
+ "coop.mv": 1,
+ "coop.mw": 1,
+ "coop.tt": 1,
+ "copenhagen.museum": 1,
+ "corporation.museum": 1,
+ "correios-e-telecomunica\u00e7\u00f5es.museum": 1,
+ "corvette.museum": 1,
+ "cosenza.it": 1,
+ "costume.museum": 1,
+ "council.aero": 1,
+ "countryestate.museum": 1,
+ "county.museum": 1,
+ "cpa.pro": 1,
+ "cq.cn": 1,
+ "cr.it": 1,
+ "cr.ua": 1,
+ "crafts.museum": 1,
+ "cranbrook.museum": 1,
+ "creation.museum": 1,
+ "cremona.it": 1,
+ "crew.aero": 1,
+ "crimea.ua": 1,
+ "crotone.it": 1,
+ "cs.it": 1,
+ "csiro.au": 1,
+ "ct.it": 1,
+ "ct.us": 1,
+ "cultural.museum": 1,
+ "culturalcenter.museum": 1,
+ "culture.museum": 1,
+ "cuneo.it": 1,
+ "cv.ua": 1,
+ "cy": 2,
+ "cyber.museum": 1,
+ "cymru.museum": 1,
+ "cz.it": 1,
+ "czeladz.pl": 1,
+ "czest.pl": 1,
+ "d.bg": 1,
+ "d.se": 1,
+ "daegu.kr": 1,
+ "daejeon.kr": 1,
+ "dagestan.ru": 1,
+ "daigo.ibaraki.jp": 1,
+ "daisen.akita.jp": 1,
+ "daito.osaka.jp": 1,
+ "daiwa.hiroshima.jp": 1,
+ "dali.museum": 1,
+ "dallas.museum": 1,
+ "database.museum": 1,
+ "date.fukushima.jp": 1,
+ "date.hokkaido.jp": 1,
+ "davvenjarga.no": 1,
+ "davvenj\u00e1rga.no": 1,
+ "davvesiida.no": 1,
+ "dazaifu.fukuoka.jp": 1,
+ "dc.us": 1,
+ "ddr.museum": 1,
+ "de.com": 1,
+ "de.us": 1,
+ "deatnu.no": 1,
+ "decorativearts.museum": 1,
+ "defense.tn": 1,
+ "delaware.museum": 1,
+ "dell-ogliastra.it": 1,
+ "dellogliastra.it": 1,
+ "delmenhorst.museum": 1,
+ "denmark.museum": 1,
+ "dep.no": 1,
+ "depot.museum": 1,
+ "design.aero": 1,
+ "design.museum": 1,
+ "detroit.museum": 1,
+ "dgca.aero": 1,
+ "dielddanuorri.no": 1,
+ "dinosaur.museum": 1,
+ "discovery.museum": 1,
+ "divtasvuodna.no": 1,
+ "divttasvuotna.no": 1,
+ "dlugoleka.pl": 1,
+ "dn.ua": 1,
+ "dnepropetrovsk.ua": 1,
+ "dni.us": 1,
+ "dnipropetrovsk.ua": 1,
+ "dnsalias.com": 1,
+ "dnsalias.net": 1,
+ "dnsalias.org": 1,
+ "dnsdojo.com": 1,
+ "dnsdojo.net": 1,
+ "dnsdojo.org": 1,
+ "does-it.net": 1,
+ "doesntexist.com": 1,
+ "doesntexist.org": 1,
+ "dolls.museum": 1,
+ "dominic.ua": 1,
+ "donetsk.ua": 1,
+ "donna.no": 1,
+ "donostia.museum": 1,
+ "dontexist.com": 1,
+ "dontexist.net": 1,
+ "dontexist.org": 1,
+ "doomdns.com": 1,
+ "doomdns.org": 1,
+ "doshi.yamanashi.jp": 1,
+ "dovre.no": 1,
+ "dp.ua": 1,
+ "dr.na": 1,
+ "drammen.no": 1,
+ "drangedal.no": 1,
+ "dreamhosters.com": 1,
+ "drobak.no": 1,
+ "dr\u00f8bak.no": 1,
+ "dudinka.ru": 1,
+ "durham.museum": 1,
+ "dvrdns.org": 1,
+ "dyn-o-saur.com": 1,
+ "dynalias.com": 1,
+ "dynalias.net": 1,
+ "dynalias.org": 1,
+ "dynathome.net": 1,
+ "dyndns-at-home.com": 1,
+ "dyndns-at-work.com": 1,
+ "dyndns-blog.com": 1,
+ "dyndns-free.com": 1,
+ "dyndns-home.com": 1,
+ "dyndns-ip.com": 1,
+ "dyndns-mail.com": 1,
+ "dyndns-office.com": 1,
+ "dyndns-pics.com": 1,
+ "dyndns-remote.com": 1,
+ "dyndns-server.com": 1,
+ "dyndns-web.com": 1,
+ "dyndns-wiki.com": 1,
+ "dyndns-work.com": 1,
+ "dyndns.biz": 1,
+ "dyndns.info": 1,
+ "dyndns.org": 1,
+ "dyndns.tv": 1,
+ "dyndns.ws": 1,
+ "dyroy.no": 1,
+ "dyr\u00f8y.no": 1,
+ "d\u00f8nna.no": 1,
+ "e-burg.ru": 1,
+ "e.bg": 1,
+ "e.se": 1,
+ "e12.ve": 1,
+ "e164.arpa": 1,
+ "eastafrica.museum": 1,
+ "eastcoast.museum": 1,
+ "ebetsu.hokkaido.jp": 1,
+ "ebina.kanagawa.jp": 1,
+ "ebino.miyazaki.jp": 1,
+ "ebiz.tw": 1,
+ "echizen.fukui.jp": 1,
+ "ecn.br": 1,
+ "eco.br": 1,
+ "ed.ao": 1,
+ "ed.ci": 1,
+ "ed.cr": 1,
+ "ed.jp": 1,
+ "ed.pw": 1,
+ "edogawa.tokyo.jp": 1,
+ "edu.ac": 1,
+ "edu.af": 1,
+ "edu.al": 1,
+ "edu.an": 1,
+ "edu.au": 1,
+ "edu.az": 1,
+ "edu.ba": 1,
+ "edu.bb": 1,
+ "edu.bh": 1,
+ "edu.bi": 1,
+ "edu.bm": 1,
+ "edu.bo": 1,
+ "edu.br": 1,
+ "edu.bs": 1,
+ "edu.bt": 1,
+ "edu.bz": 1,
+ "edu.ci": 1,
+ "edu.cn": 1,
+ "edu.co": 1,
+ "edu.cu": 1,
+ "edu.dm": 1,
+ "edu.do": 1,
+ "edu.dz": 1,
+ "edu.ec": 1,
+ "edu.ee": 1,
+ "edu.eg": 1,
+ "edu.es": 1,
+ "edu.ge": 1,
+ "edu.gh": 1,
+ "edu.gi": 1,
+ "edu.gn": 1,
+ "edu.gp": 1,
+ "edu.gr": 1,
+ "edu.hk": 1,
+ "edu.hn": 1,
+ "edu.ht": 1,
+ "edu.in": 1,
+ "edu.iq": 1,
+ "edu.is": 1,
+ "edu.it": 1,
+ "edu.jo": 1,
+ "edu.kg": 1,
+ "edu.ki": 1,
+ "edu.km": 1,
+ "edu.kn": 1,
+ "edu.kp": 1,
+ "edu.ky": 1,
+ "edu.kz": 1,
+ "edu.la": 1,
+ "edu.lb": 1,
+ "edu.lc": 1,
+ "edu.lk": 1,
+ "edu.lr": 1,
+ "edu.lv": 1,
+ "edu.ly": 1,
+ "edu.me": 1,
+ "edu.mg": 1,
+ "edu.mk": 1,
+ "edu.ml": 1,
+ "edu.mn": 1,
+ "edu.mo": 1,
+ "edu.mv": 1,
+ "edu.mw": 1,
+ "edu.mx": 1,
+ "edu.my": 1,
+ "edu.ng": 1,
+ "edu.nr": 1,
+ "edu.pa": 1,
+ "edu.pe": 1,
+ "edu.pf": 1,
+ "edu.ph": 1,
+ "edu.pk": 1,
+ "edu.pl": 1,
+ "edu.pn": 1,
+ "edu.pr": 1,
+ "edu.ps": 1,
+ "edu.pt": 1,
+ "edu.qa": 1,
+ "edu.rs": 1,
+ "edu.ru": 1,
+ "edu.rw": 1,
+ "edu.sa": 1,
+ "edu.sb": 1,
+ "edu.sc": 1,
+ "edu.sd": 1,
+ "edu.sg": 1,
+ "edu.sl": 1,
+ "edu.sn": 1,
+ "edu.st": 1,
+ "edu.sy": 1,
+ "edu.tj": 1,
+ "edu.tm": 1,
+ "edu.to": 1,
+ "edu.tt": 1,
+ "edu.tw": 1,
+ "edu.ua": 1,
+ "edu.uy": 1,
+ "edu.vc": 1,
+ "edu.ve": 1,
+ "edu.vn": 1,
+ "edu.ws": 1,
+ "educ.ar": 0,
+ "education.museum": 1,
+ "educational.museum": 1,
+ "educator.aero": 1,
+ "edunet.tn": 1,
+ "egersund.no": 1,
+ "egyptian.museum": 1,
+ "ehime.jp": 1,
+ "eid.no": 1,
+ "eidfjord.no": 1,
+ "eidsberg.no": 1,
+ "eidskog.no": 1,
+ "eidsvoll.no": 1,
+ "eigersund.no": 1,
+ "eiheiji.fukui.jp": 1,
+ "eisenbahn.museum": 1,
+ "elblag.pl": 1,
+ "elburg.museum": 1,
+ "elk.pl": 1,
+ "elvendrell.museum": 1,
+ "elverum.no": 1,
+ "embaixada.st": 1,
+ "embetsu.hokkaido.jp": 1,
+ "embroidery.museum": 1,
+ "emergency.aero": 1,
+ "emp.br": 1,
+ "en.it": 1,
+ "ena.gifu.jp": 1,
+ "encyclopedic.museum": 1,
+ "endofinternet.net": 1,
+ "endofinternet.org": 1,
+ "endoftheinternet.org": 1,
+ "enebakk.no": 1,
+ "eng.br": 1,
+ "eng.pro": 1,
+ "engerdal.no": 1,
+ "engine.aero": 1,
+ "engineer.aero": 1,
+ "england.museum": 1,
+ "eniwa.hokkaido.jp": 1,
+ "enna.it": 1,
+ "ens.tn": 1,
+ "entertainment.aero": 1,
+ "entomology.museum": 1,
+ "environment.museum": 1,
+ "environmentalconservation.museum": 1,
+ "epilepsy.museum": 1,
+ "equipment.aero": 1,
+ "er": 2,
+ "erimo.hokkaido.jp": 1,
+ "erotica.hu": 1,
+ "erotika.hu": 1,
+ "es.kr": 1,
+ "esan.hokkaido.jp": 1,
+ "esashi.hokkaido.jp": 1,
+ "esp.br": 1,
+ "essex.museum": 1,
+ "est-a-la-maison.com": 1,
+ "est-a-la-masion.com": 1,
+ "est-le-patron.com": 1,
+ "est-mon-blogueur.com": 1,
+ "est.pr": 1,
+ "estate.museum": 1,
+ "et": 2,
+ "etajima.hiroshima.jp": 1,
+ "etc.br": 1,
+ "ethnology.museum": 1,
+ "eti.br": 1,
+ "etne.no": 1,
+ "etnedal.no": 1,
+ "eu.com": 1,
+ "eu.int": 1,
+ "eun.eg": 1,
+ "evenassi.no": 1,
+ "evenes.no": 1,
+ "even\u00e1\u0161\u0161i.no": 1,
+ "evje-og-hornnes.no": 1,
+ "exchange.aero": 1,
+ "exeter.museum": 1,
+ "exhibition.museum": 1,
+ "experts-comptables.fr": 1,
+ "express.aero": 1,
+ "f.bg": 1,
+ "f.se": 1,
+ "fam.pk": 1,
+ "family.museum": 1,
+ "far.br": 1,
+ "fareast.ru": 1,
+ "farm.museum": 1,
+ "farmequipment.museum": 1,
+ "farmers.museum": 1,
+ "farmstead.museum": 1,
+ "farsund.no": 1,
+ "fauske.no": 1,
+ "fc.it": 1,
+ "fe.it": 1,
+ "fed.us": 1,
+ "federation.aero": 1,
+ "fedje.no": 1,
+ "fermo.it": 1,
+ "ferrara.it": 1,
+ "fet.no": 1,
+ "fetsund.no": 1,
+ "fg.it": 1,
+ "fh.se": 1,
+ "fhs.no": 1,
+ "fhsk.se": 1,
+ "fhv.se": 1,
+ "fi.cr": 1,
+ "fi.it": 1,
+ "fie.ee": 1,
+ "field.museum": 1,
+ "figueres.museum": 1,
+ "filatelia.museum": 1,
+ "film.hu": 1,
+ "film.museum": 1,
+ "fin.ec": 1,
+ "fin.tn": 1,
+ "fineart.museum": 1,
+ "finearts.museum": 1,
+ "finland.museum": 1,
+ "finnoy.no": 1,
+ "finn\u00f8y.no": 1,
+ "firenze.it": 1,
+ "firm.co": 1,
+ "firm.ht": 1,
+ "firm.in": 1,
+ "firm.nf": 1,
+ "firm.ro": 1,
+ "fitjar.no": 1,
+ "fj": 2,
+ "fj.cn": 1,
+ "fjaler.no": 1,
+ "fjell.no": 1,
+ "fk": 2,
+ "fl.us": 1,
+ "fla.no": 1,
+ "flakstad.no": 1,
+ "flanders.museum": 1,
+ "flatanger.no": 1,
+ "flekkefjord.no": 1,
+ "flesberg.no": 1,
+ "flight.aero": 1,
+ "flog.br": 1,
+ "flora.no": 1,
+ "florence.it": 1,
+ "florida.museum": 1,
+ "floro.no": 1,
+ "flor\u00f8.no": 1,
+ "fl\u00e5.no": 1,
+ "fm.br": 1,
+ "fm.it": 1,
+ "fm.no": 1,
+ "fnd.br": 1,
+ "foggia.it": 1,
+ "folkebibl.no": 1,
+ "folldal.no": 1,
+ "for-better.biz": 1,
+ "for-more.biz": 1,
+ "for-our.info": 1,
+ "for-some.biz": 1,
+ "for-the.biz": 1,
+ "force.museum": 1,
+ "forde.no": 1,
+ "forgot.her.name": 1,
+ "forgot.his.name": 1,
+ "forli-cesena.it": 1,
+ "forlicesena.it": 1,
+ "forsand.no": 1,
+ "fortmissoula.museum": 1,
+ "fortworth.museum": 1,
+ "forum.hu": 1,
+ "fosnes.no": 1,
+ "fot.br": 1,
+ "foundation.museum": 1,
+ "fr.it": 1,
+ "frana.no": 1,
+ "francaise.museum": 1,
+ "frankfurt.museum": 1,
+ "franziskaner.museum": 1,
+ "fredrikstad.no": 1,
+ "freemasonry.museum": 1,
+ "frei.no": 1,
+ "freiburg.museum": 1,
+ "freight.aero": 1,
+ "fribourg.museum": 1,
+ "frog.museum": 1,
+ "frogn.no": 1,
+ "froland.no": 1,
+ "from-ak.com": 1,
+ "from-al.com": 1,
+ "from-ar.com": 1,
+ "from-az.net": 1,
+ "from-ca.com": 1,
+ "from-co.net": 1,
+ "from-ct.com": 1,
+ "from-dc.com": 1,
+ "from-de.com": 1,
+ "from-fl.com": 1,
+ "from-ga.com": 1,
+ "from-hi.com": 1,
+ "from-ia.com": 1,
+ "from-id.com": 1,
+ "from-il.com": 1,
+ "from-in.com": 1,
+ "from-ks.com": 1,
+ "from-ky.com": 1,
+ "from-la.net": 1,
+ "from-ma.com": 1,
+ "from-md.com": 1,
+ "from-me.org": 1,
+ "from-mi.com": 1,
+ "from-mn.com": 1,
+ "from-mo.com": 1,
+ "from-ms.com": 1,
+ "from-mt.com": 1,
+ "from-nc.com": 1,
+ "from-nd.com": 1,
+ "from-ne.com": 1,
+ "from-nh.com": 1,
+ "from-nj.com": 1,
+ "from-nm.com": 1,
+ "from-nv.com": 1,
+ "from-ny.net": 1,
+ "from-oh.com": 1,
+ "from-ok.com": 1,
+ "from-or.com": 1,
+ "from-pa.com": 1,
+ "from-pr.com": 1,
+ "from-ri.com": 1,
+ "from-sc.com": 1,
+ "from-sd.com": 1,
+ "from-tn.com": 1,
+ "from-tx.com": 1,
+ "from-ut.com": 1,
+ "from-va.com": 1,
+ "from-vt.com": 1,
+ "from-wa.com": 1,
+ "from-wi.com": 1,
+ "from-wv.com": 1,
+ "from-wy.com": 1,
+ "from.hr": 1,
+ "frosinone.it": 1,
+ "frosta.no": 1,
+ "froya.no": 1,
+ "fr\u00e6na.no": 1,
+ "fr\u00f8ya.no": 1,
+ "fst.br": 1,
+ "ftpaccess.cc": 1,
+ "fuchu.hiroshima.jp": 1,
+ "fuchu.tokyo.jp": 1,
+ "fuchu.toyama.jp": 1,
+ "fudai.iwate.jp": 1,
+ "fuefuki.yamanashi.jp": 1,
+ "fuel.aero": 1,
+ "fuettertdasnetz.de": 1,
+ "fuji.shizuoka.jp": 1,
+ "fujieda.shizuoka.jp": 1,
+ "fujiidera.osaka.jp": 1,
+ "fujikawa.shizuoka.jp": 1,
+ "fujikawa.yamanashi.jp": 1,
+ "fujikawaguchiko.yamanashi.jp": 1,
+ "fujimi.nagano.jp": 1,
+ "fujimi.saitama.jp": 1,
+ "fujimino.saitama.jp": 1,
+ "fujinomiya.shizuoka.jp": 1,
+ "fujioka.gunma.jp": 1,
+ "fujisato.akita.jp": 1,
+ "fujisawa.iwate.jp": 1,
+ "fujisawa.kanagawa.jp": 1,
+ "fujishiro.ibaraki.jp": 1,
+ "fujiyoshida.yamanashi.jp": 1,
+ "fukagawa.hokkaido.jp": 1,
+ "fukaya.saitama.jp": 1,
+ "fukuchi.fukuoka.jp": 1,
+ "fukuchiyama.kyoto.jp": 1,
+ "fukudomi.saga.jp": 1,
+ "fukui.fukui.jp": 1,
+ "fukui.jp": 1,
+ "fukumitsu.toyama.jp": 1,
+ "fukuoka.jp": 1,
+ "fukuroi.shizuoka.jp": 1,
+ "fukusaki.hyogo.jp": 1,
+ "fukushima.fukushima.jp": 1,
+ "fukushima.hokkaido.jp": 1,
+ "fukushima.jp": 1,
+ "fukuyama.hiroshima.jp": 1,
+ "funabashi.chiba.jp": 1,
+ "funagata.yamagata.jp": 1,
+ "funahashi.toyama.jp": 1,
+ "fundacio.museum": 1,
+ "fuoisku.no": 1,
+ "fuossko.no": 1,
+ "furano.hokkaido.jp": 1,
+ "furniture.museum": 1,
+ "furubira.hokkaido.jp": 1,
+ "furudono.fukushima.jp": 1,
+ "furukawa.miyagi.jp": 1,
+ "fusa.no": 1,
+ "fuso.aichi.jp": 1,
+ "fussa.tokyo.jp": 1,
+ "futaba.fukushima.jp": 1,
+ "futsu.nagasaki.jp": 1,
+ "futtsu.chiba.jp": 1,
+ "fylkesbibl.no": 1,
+ "fyresdal.no": 1,
+ "f\u00f8rde.no": 1,
+ "g.bg": 1,
+ "g.se": 1,
+ "g12.br": 1,
+ "ga.us": 1,
+ "gaivuotna.no": 1,
+ "gallery.museum": 1,
+ "galsa.no": 1,
+ "gamagori.aichi.jp": 1,
+ "game-host.org": 1,
+ "game-server.cc": 1,
+ "game.tw": 1,
+ "games.hu": 1,
+ "gamo.shiga.jp": 1,
+ "gamvik.no": 1,
+ "gangaviika.no": 1,
+ "gangwon.kr": 1,
+ "garden.museum": 1,
+ "gateway.museum": 1,
+ "gaular.no": 1,
+ "gausdal.no": 1,
+ "gb.com": 1,
+ "gb.net": 1,
+ "gc.ca": 1,
+ "gd.cn": 1,
+ "gda.pl": 1,
+ "gdansk.pl": 1,
+ "gdynia.pl": 1,
+ "ge.it": 1,
+ "geelvinck.museum": 1,
+ "geisei.kochi.jp": 1,
+ "gemological.museum": 1,
+ "gen.in": 1,
+ "genkai.saga.jp": 1,
+ "genoa.it": 1,
+ "genova.it": 1,
+ "geology.museum": 1,
+ "geometre-expert.fr": 1,
+ "georgia.museum": 1,
+ "getmyip.com": 1,
+ "gets-it.net": 1,
+ "ggf.br": 1,
+ "giehtavuoatna.no": 1,
+ "giessen.museum": 1,
+ "gifu.gifu.jp": 1,
+ "gifu.jp": 1,
+ "gildeskal.no": 1,
+ "gildesk\u00e5l.no": 1,
+ "ginan.gifu.jp": 1,
+ "ginowan.okinawa.jp": 1,
+ "ginoza.okinawa.jp": 1,
+ "giske.no": 1,
+ "gjemnes.no": 1,
+ "gjerdrum.no": 1,
+ "gjerstad.no": 1,
+ "gjesdal.no": 1,
+ "gjovik.no": 1,
+ "gj\u00f8vik.no": 1,
+ "glas.museum": 1,
+ "glass.museum": 1,
+ "gliding.aero": 1,
+ "gliwice.pl": 1,
+ "glogow.pl": 1,
+ "gloppen.no": 1,
+ "gmina.pl": 1,
+ "gniezno.pl": 1,
+ "go.ci": 1,
+ "go.cr": 1,
+ "go.dyndns.org": 1,
+ "go.id": 1,
+ "go.it": 1,
+ "go.jp": 1,
+ "go.kr": 1,
+ "go.pw": 1,
+ "go.th": 1,
+ "go.tj": 1,
+ "go.tz": 1,
+ "go.ug": 1,
+ "gob.bo": 1,
+ "gob.cl": 1,
+ "gob.do": 1,
+ "gob.ec": 1,
+ "gob.es": 1,
+ "gob.hn": 1,
+ "gob.mx": 1,
+ "gob.pa": 1,
+ "gob.pe": 1,
+ "gob.pk": 1,
+ "gobiernoelectronico.ar": 0,
+ "gobo.wakayama.jp": 1,
+ "godo.gifu.jp": 1,
+ "gojome.akita.jp": 1,
+ "gok.pk": 1,
+ "gokase.miyazaki.jp": 1,
+ "gol.no": 1,
+ "gon.pk": 1,
+ "gonohe.aomori.jp": 1,
+ "gop.pk": 1,
+ "gorge.museum": 1,
+ "gorizia.it": 1,
+ "gorlice.pl": 1,
+ "gos.pk": 1,
+ "gose.nara.jp": 1,
+ "gosen.niigata.jp": 1,
+ "goshiki.hyogo.jp": 1,
+ "gotdns.com": 1,
+ "gotdns.org": 1,
+ "gotemba.shizuoka.jp": 1,
+ "goto.nagasaki.jp": 1,
+ "gotsu.shimane.jp": 1,
+ "gouv.bj": 1,
+ "gouv.ci": 1,
+ "gouv.fr": 1,
+ "gouv.ht": 1,
+ "gouv.km": 1,
+ "gouv.ml": 1,
+ "gouv.rw": 1,
+ "gouv.sn": 1,
+ "gov.ac": 1,
+ "gov.ae": 1,
+ "gov.af": 1,
+ "gov.al": 1,
+ "gov.as": 1,
+ "gov.au": 1,
+ "gov.az": 1,
+ "gov.ba": 1,
+ "gov.bb": 1,
+ "gov.bf": 1,
+ "gov.bh": 1,
+ "gov.bm": 1,
+ "gov.bo": 1,
+ "gov.br": 1,
+ "gov.bs": 1,
+ "gov.bt": 1,
+ "gov.by": 1,
+ "gov.bz": 1,
+ "gov.cd": 1,
+ "gov.cl": 1,
+ "gov.cm": 1,
+ "gov.cn": 1,
+ "gov.co": 1,
+ "gov.cu": 1,
+ "gov.cx": 1,
+ "gov.dm": 1,
+ "gov.do": 1,
+ "gov.dz": 1,
+ "gov.ec": 1,
+ "gov.ee": 1,
+ "gov.eg": 1,
+ "gov.ge": 1,
+ "gov.gg": 1,
+ "gov.gh": 1,
+ "gov.gi": 1,
+ "gov.gn": 1,
+ "gov.gr": 1,
+ "gov.hk": 1,
+ "gov.ie": 1,
+ "gov.im": 1,
+ "gov.in": 1,
+ "gov.iq": 1,
+ "gov.ir": 1,
+ "gov.is": 1,
+ "gov.it": 1,
+ "gov.je": 1,
+ "gov.jo": 1,
+ "gov.kg": 1,
+ "gov.ki": 1,
+ "gov.km": 1,
+ "gov.kn": 1,
+ "gov.kp": 1,
+ "gov.ky": 1,
+ "gov.kz": 1,
+ "gov.la": 1,
+ "gov.lb": 1,
+ "gov.lc": 1,
+ "gov.lk": 1,
+ "gov.lr": 1,
+ "gov.lt": 1,
+ "gov.lv": 1,
+ "gov.ly": 1,
+ "gov.ma": 1,
+ "gov.me": 1,
+ "gov.mg": 1,
+ "gov.mk": 1,
+ "gov.ml": 1,
+ "gov.mn": 1,
+ "gov.mo": 1,
+ "gov.mr": 1,
+ "gov.mu": 1,
+ "gov.mv": 1,
+ "gov.mw": 1,
+ "gov.my": 1,
+ "gov.nc.tr": 1,
+ "gov.ng": 1,
+ "gov.nr": 1,
+ "gov.ph": 1,
+ "gov.pk": 1,
+ "gov.pl": 1,
+ "gov.pn": 1,
+ "gov.pr": 1,
+ "gov.ps": 1,
+ "gov.pt": 1,
+ "gov.qa": 1,
+ "gov.rs": 1,
+ "gov.ru": 1,
+ "gov.rw": 1,
+ "gov.sa": 1,
+ "gov.sb": 1,
+ "gov.sc": 1,
+ "gov.sd": 1,
+ "gov.sg": 1,
+ "gov.sh": 1,
+ "gov.sl": 1,
+ "gov.st": 1,
+ "gov.sx": 1,
+ "gov.sy": 1,
+ "gov.tj": 1,
+ "gov.tl": 1,
+ "gov.tm": 1,
+ "gov.tn": 1,
+ "gov.to": 1,
+ "gov.tt": 1,
+ "gov.tw": 1,
+ "gov.ua": 1,
+ "gov.vc": 1,
+ "gov.ve": 1,
+ "gov.vn": 1,
+ "gov.ws": 1,
+ "government.aero": 1,
+ "gr.com": 1,
+ "gr.it": 1,
+ "gr.jp": 1,
+ "grajewo.pl": 1,
+ "gran.no": 1,
+ "grandrapids.museum": 1,
+ "grane.no": 1,
+ "granvin.no": 1,
+ "gratangen.no": 1,
+ "graz.museum": 1,
+ "greta.fr": 1,
+ "grimstad.no": 1,
+ "groks-the.info": 1,
+ "groks-this.info": 1,
+ "grong.no": 1,
+ "grosseto.it": 1,
+ "groundhandling.aero": 1,
+ "group.aero": 1,
+ "grozny.ru": 1,
+ "grp.lk": 1,
+ "grue.no": 1,
+ "gs.aa.no": 1,
+ "gs.ah.no": 1,
+ "gs.bu.no": 1,
+ "gs.cn": 1,
+ "gs.fm.no": 1,
+ "gs.hl.no": 1,
+ "gs.hm.no": 1,
+ "gs.jan-mayen.no": 1,
+ "gs.mr.no": 1,
+ "gs.nl.no": 1,
+ "gs.nt.no": 1,
+ "gs.of.no": 1,
+ "gs.ol.no": 1,
+ "gs.oslo.no": 1,
+ "gs.rl.no": 1,
+ "gs.sf.no": 1,
+ "gs.st.no": 1,
+ "gs.svalbard.no": 1,
+ "gs.tm.no": 1,
+ "gs.tr.no": 1,
+ "gs.va.no": 1,
+ "gs.vf.no": 1,
+ "gsm.pl": 1,
+ "gt": 2,
+ "gu": 2,
+ "gu.us": 1,
+ "gub.uy": 1,
+ "guernsey.museum": 1,
+ "gujo.gifu.jp": 1,
+ "gulen.no": 1,
+ "gunma.jp": 1,
+ "guovdageaidnu.no": 1,
+ "gushikami.okinawa.jp": 1,
+ "gv.ao": 1,
+ "gv.at": 1,
+ "gwangju.kr": 1,
+ "gx.cn": 1,
+ "gyeongbuk.kr": 1,
+ "gyeonggi.kr": 1,
+ "gyeongnam.kr": 1,
+ "gyokuto.kumamoto.jp": 1,
+ "gz.cn": 1,
+ "g\u00e1ivuotna.no": 1,
+ "g\u00e1ls\u00e1.no": 1,
+ "g\u00e1\u014bgaviika.no": 1,
+ "h.bg": 1,
+ "h.se": 1,
+ "ha.cn": 1,
+ "ha.no": 1,
+ "habikino.osaka.jp": 1,
+ "habmer.no": 1,
+ "haboro.hokkaido.jp": 1,
+ "hachijo.tokyo.jp": 1,
+ "hachinohe.aomori.jp": 1,
+ "hachioji.tokyo.jp": 1,
+ "hachirogata.akita.jp": 1,
+ "hadano.kanagawa.jp": 1,
+ "hadsel.no": 1,
+ "haebaru.okinawa.jp": 1,
+ "haga.tochigi.jp": 1,
+ "hagebostad.no": 1,
+ "hagi.yamaguchi.jp": 1,
+ "haibara.shizuoka.jp": 1,
+ "hakata.fukuoka.jp": 1,
+ "hakodate.hokkaido.jp": 1,
+ "hakone.kanagawa.jp": 1,
+ "hakuba.nagano.jp": 1,
+ "hakui.ishikawa.jp": 1,
+ "hakusan.ishikawa.jp": 1,
+ "halden.no": 1,
+ "halloffame.museum": 1,
+ "halsa.no": 1,
+ "ham-radio-op.net": 1,
+ "hamada.shimane.jp": 1,
+ "hamamatsu.shizuoka.jp": 1,
+ "hamar.no": 1,
+ "hamaroy.no": 1,
+ "hamatama.saga.jp": 1,
+ "hamatonbetsu.hokkaido.jp": 1,
+ "hamburg.museum": 1,
+ "hammarfeasta.no": 1,
+ "hammerfest.no": 1,
+ "hamura.tokyo.jp": 1,
+ "hanamaki.iwate.jp": 1,
+ "hanamigawa.chiba.jp": 1,
+ "hanawa.fukushima.jp": 1,
+ "handa.aichi.jp": 1,
+ "handson.museum": 1,
+ "hanggliding.aero": 1,
+ "hannan.osaka.jp": 1,
+ "hanno.saitama.jp": 1,
+ "hanyu.saitama.jp": 1,
+ "hapmir.no": 1,
+ "happou.akita.jp": 1,
+ "hara.nagano.jp": 1,
+ "haram.no": 1,
+ "hareid.no": 1,
+ "harima.hyogo.jp": 1,
+ "harstad.no": 1,
+ "harvestcelebration.museum": 1,
+ "hasama.oita.jp": 1,
+ "hasami.nagasaki.jp": 1,
+ "hashikami.aomori.jp": 1,
+ "hashima.gifu.jp": 1,
+ "hashimoto.wakayama.jp": 1,
+ "hasuda.saitama.jp": 1,
+ "hasvik.no": 1,
+ "hatogaya.saitama.jp": 1,
+ "hatoyama.saitama.jp": 1,
+ "hatsukaichi.hiroshima.jp": 1,
+ "hattfjelldal.no": 1,
+ "haugesund.no": 1,
+ "hawaii.museum": 1,
+ "hayakawa.yamanashi.jp": 1,
+ "hayashima.okayama.jp": 1,
+ "hazu.aichi.jp": 1,
+ "hb.cn": 1,
+ "he.cn": 1,
+ "health.museum": 1,
+ "health.vn": 1,
+ "heguri.nara.jp": 1,
+ "heimatunduhren.museum": 1,
+ "hekinan.aichi.jp": 1,
+ "hellas.museum": 1,
+ "helsinki.museum": 1,
+ "hembygdsforbund.museum": 1,
+ "hemne.no": 1,
+ "hemnes.no": 1,
+ "hemsedal.no": 1,
+ "herad.no": 1,
+ "here-for-more.info": 1,
+ "heritage.museum": 1,
+ "heroy.more-og-romsdal.no": 1,
+ "heroy.nordland.no": 1,
+ "her\u00f8y.m\u00f8re-og-romsdal.no": 1,
+ "her\u00f8y.nordland.no": 1,
+ "hi.cn": 1,
+ "hi.us": 1,
+ "hichiso.gifu.jp": 1,
+ "hida.gifu.jp": 1,
+ "hidaka.hokkaido.jp": 1,
+ "hidaka.kochi.jp": 1,
+ "hidaka.saitama.jp": 1,
+ "hidaka.wakayama.jp": 1,
+ "higashi.fukuoka.jp": 1,
+ "higashi.fukushima.jp": 1,
+ "higashi.okinawa.jp": 1,
+ "higashiagatsuma.gunma.jp": 1,
+ "higashichichibu.saitama.jp": 1,
+ "higashihiroshima.hiroshima.jp": 1,
+ "higashiizu.shizuoka.jp": 1,
+ "higashiizumo.shimane.jp": 1,
+ "higashikagawa.kagawa.jp": 1,
+ "higashikagura.hokkaido.jp": 1,
+ "higashikawa.hokkaido.jp": 1,
+ "higashikurume.tokyo.jp": 1,
+ "higashimatsushima.miyagi.jp": 1,
+ "higashimatsuyama.saitama.jp": 1,
+ "higashimurayama.tokyo.jp": 1,
+ "higashinaruse.akita.jp": 1,
+ "higashine.yamagata.jp": 1,
+ "higashiomi.shiga.jp": 1,
+ "higashiosaka.osaka.jp": 1,
+ "higashishirakawa.gifu.jp": 1,
+ "higashisumiyoshi.osaka.jp": 1,
+ "higashitsuno.kochi.jp": 1,
+ "higashiura.aichi.jp": 1,
+ "higashiyama.kyoto.jp": 1,
+ "higashiyamato.tokyo.jp": 1,
+ "higashiyodogawa.osaka.jp": 1,
+ "higashiyoshino.nara.jp": 1,
+ "hiji.oita.jp": 1,
+ "hikari.yamaguchi.jp": 1,
+ "hikawa.shimane.jp": 1,
+ "hikimi.shimane.jp": 1,
+ "hikone.shiga.jp": 1,
+ "himeji.hyogo.jp": 1,
+ "himeshima.oita.jp": 1,
+ "himi.toyama.jp": 1,
+ "hino.tokyo.jp": 1,
+ "hino.tottori.jp": 1,
+ "hinode.tokyo.jp": 1,
+ "hinohara.tokyo.jp": 1,
+ "hioki.kagoshima.jp": 1,
+ "hirado.nagasaki.jp": 1,
+ "hiraizumi.iwate.jp": 1,
+ "hirakata.osaka.jp": 1,
+ "hiranai.aomori.jp": 1,
+ "hirara.okinawa.jp": 1,
+ "hirata.fukushima.jp": 1,
+ "hiratsuka.kanagawa.jp": 1,
+ "hiraya.nagano.jp": 1,
+ "hirogawa.wakayama.jp": 1,
+ "hirokawa.fukuoka.jp": 1,
+ "hirono.fukushima.jp": 1,
+ "hirono.iwate.jp": 1,
+ "hiroo.hokkaido.jp": 1,
+ "hirosaki.aomori.jp": 1,
+ "hiroshima.jp": 1,
+ "hisayama.fukuoka.jp": 1,
+ "histoire.museum": 1,
+ "historical.museum": 1,
+ "historicalsociety.museum": 1,
+ "historichouses.museum": 1,
+ "historisch.museum": 1,
+ "historisches.museum": 1,
+ "history.museum": 1,
+ "historyofscience.museum": 1,
+ "hita.oita.jp": 1,
+ "hitachi.ibaraki.jp": 1,
+ "hitachinaka.ibaraki.jp": 1,
+ "hitachiomiya.ibaraki.jp": 1,
+ "hitachiota.ibaraki.jp": 1,
+ "hitoyoshi.kumamoto.jp": 1,
+ "hitra.no": 1,
+ "hizen.saga.jp": 1,
+ "hjartdal.no": 1,
+ "hjelmeland.no": 1,
+ "hk.cn": 1,
+ "hl.cn": 1,
+ "hl.no": 1,
+ "hm.no": 1,
+ "hn.cn": 1,
+ "hobby-site.com": 1,
+ "hobby-site.org": 1,
+ "hobol.no": 1,
+ "hob\u00f8l.no": 1,
+ "hof.no": 1,
+ "hofu.yamaguchi.jp": 1,
+ "hokkaido.jp": 1,
+ "hokksund.no": 1,
+ "hokuryu.hokkaido.jp": 1,
+ "hokuto.hokkaido.jp": 1,
+ "hokuto.yamanashi.jp": 1,
+ "hol.no": 1,
+ "hole.no": 1,
+ "holmestrand.no": 1,
+ "holtalen.no": 1,
+ "holt\u00e5len.no": 1,
+ "home.dyndns.org": 1,
+ "homebuilt.aero": 1,
+ "homedns.org": 1,
+ "homeftp.net": 1,
+ "homeftp.org": 1,
+ "homeip.net": 1,
+ "homelinux.com": 1,
+ "homelinux.net": 1,
+ "homelinux.org": 1,
+ "homeunix.com": 1,
+ "homeunix.net": 1,
+ "homeunix.org": 1,
+ "honai.ehime.jp": 1,
+ "honbetsu.hokkaido.jp": 1,
+ "honefoss.no": 1,
+ "hongo.hiroshima.jp": 1,
+ "honjo.akita.jp": 1,
+ "honjo.saitama.jp": 1,
+ "honjyo.akita.jp": 1,
+ "hornindal.no": 1,
+ "horokanai.hokkaido.jp": 1,
+ "horology.museum": 1,
+ "horonobe.hokkaido.jp": 1,
+ "horten.no": 1,
+ "hotel.hu": 1,
+ "hotel.lk": 1,
+ "house.museum": 1,
+ "hoyanger.no": 1,
+ "hoylandet.no": 1,
+ "hs.kr": 1,
+ "hu.com": 1,
+ "hu.net": 1,
+ "huissier-justice.fr": 1,
+ "humanities.museum": 1,
+ "hurdal.no": 1,
+ "hurum.no": 1,
+ "hvaler.no": 1,
+ "hyllestad.no": 1,
+ "hyogo.jp": 1,
+ "hyuga.miyazaki.jp": 1,
+ "h\u00e1bmer.no": 1,
+ "h\u00e1mm\u00e1rfeasta.no": 1,
+ "h\u00e1pmir.no": 1,
+ "h\u00e5.no": 1,
+ "h\u00e6gebostad.no": 1,
+ "h\u00f8nefoss.no": 1,
+ "h\u00f8yanger.no": 1,
+ "h\u00f8ylandet.no": 1,
+ "i.bg": 1,
+ "i.ph": 1,
+ "i.se": 1,
+ "ia.us": 1,
+ "iamallama.com": 1,
+ "ibara.okayama.jp": 1,
+ "ibaraki.ibaraki.jp": 1,
+ "ibaraki.jp": 1,
+ "ibaraki.osaka.jp": 1,
+ "ibestad.no": 1,
+ "ibigawa.gifu.jp": 1,
+ "ichiba.tokushima.jp": 1,
+ "ichihara.chiba.jp": 1,
+ "ichikai.tochigi.jp": 1,
+ "ichikawa.chiba.jp": 1,
+ "ichikawa.hyogo.jp": 1,
+ "ichikawamisato.yamanashi.jp": 1,
+ "ichinohe.iwate.jp": 1,
+ "ichinomiya.aichi.jp": 1,
+ "ichinomiya.chiba.jp": 1,
+ "ichinoseki.iwate.jp": 1,
+ "id.au": 1,
+ "id.ir": 1,
+ "id.lv": 1,
+ "id.ly": 1,
+ "id.us": 1,
+ "ide.kyoto.jp": 1,
+ "idrett.no": 1,
+ "idv.hk": 1,
+ "idv.tw": 1,
+ "if.ua": 1,
+ "iglesias-carbonia.it": 1,
+ "iglesiascarbonia.it": 1,
+ "iheya.okinawa.jp": 1,
+ "iida.nagano.jp": 1,
+ "iide.yamagata.jp": 1,
+ "iijima.nagano.jp": 1,
+ "iitate.fukushima.jp": 1,
+ "iiyama.nagano.jp": 1,
+ "iizuka.fukuoka.jp": 1,
+ "iizuna.nagano.jp": 1,
+ "ikaruga.nara.jp": 1,
+ "ikata.ehime.jp": 1,
+ "ikawa.akita.jp": 1,
+ "ikeda.fukui.jp": 1,
+ "ikeda.gifu.jp": 1,
+ "ikeda.hokkaido.jp": 1,
+ "ikeda.nagano.jp": 1,
+ "ikeda.osaka.jp": 1,
+ "iki.fi": 1,
+ "iki.nagasaki.jp": 1,
+ "ikoma.nara.jp": 1,
+ "ikusaka.nagano.jp": 1,
+ "il": 2,
+ "il.us": 1,
+ "ilawa.pl": 1,
+ "illustration.museum": 1,
+ "im.it": 1,
+ "imabari.ehime.jp": 1,
+ "imageandsound.museum": 1,
+ "imakane.hokkaido.jp": 1,
+ "imari.saga.jp": 1,
+ "imb.br": 1,
+ "imizu.toyama.jp": 1,
+ "imperia.it": 1,
+ "in-addr.arpa": 1,
+ "in-the-band.net": 1,
+ "in.na": 1,
+ "in.rs": 1,
+ "in.th": 1,
+ "in.ua": 1,
+ "in.us": 1,
+ "ina.ibaraki.jp": 1,
+ "ina.nagano.jp": 1,
+ "ina.saitama.jp": 1,
+ "inabe.mie.jp": 1,
+ "inagawa.hyogo.jp": 1,
+ "inagi.tokyo.jp": 1,
+ "inami.toyama.jp": 1,
+ "inami.wakayama.jp": 1,
+ "inashiki.ibaraki.jp": 1,
+ "inatsuki.fukuoka.jp": 1,
+ "inawashiro.fukushima.jp": 1,
+ "inazawa.aichi.jp": 1,
+ "incheon.kr": 1,
+ "ind.br": 1,
+ "ind.in": 1,
+ "ind.tn": 1,
+ "inderoy.no": 1,
+ "inder\u00f8y.no": 1,
+ "indian.museum": 1,
+ "indiana.museum": 1,
+ "indianapolis.museum": 1,
+ "indianmarket.museum": 1,
+ "ine.kyoto.jp": 1,
+ "inf.br": 1,
+ "inf.cu": 1,
+ "inf.mk": 1,
+ "info.at": 1,
+ "info.au": 1,
+ "info.az": 1,
+ "info.bb": 1,
+ "info.co": 1,
+ "info.ec": 1,
+ "info.ht": 1,
+ "info.hu": 1,
+ "info.ki": 1,
+ "info.la": 1,
+ "info.mv": 1,
+ "info.na": 1,
+ "info.nf": 1,
+ "info.nr": 1,
+ "info.pk": 1,
+ "info.pl": 1,
+ "info.pr": 1,
+ "info.ro": 1,
+ "info.sd": 1,
+ "info.tn": 1,
+ "info.tt": 1,
+ "info.ve": 1,
+ "info.vn": 1,
+ "ing.pa": 1,
+ "ingatlan.hu": 1,
+ "ino.kochi.jp": 1,
+ "insurance.aero": 1,
+ "int.az": 1,
+ "int.bo": 1,
+ "int.ci": 1,
+ "int.co": 1,
+ "int.is": 1,
+ "int.la": 1,
+ "int.lk": 1,
+ "int.mv": 1,
+ "int.mw": 1,
+ "int.pt": 1,
+ "int.ru": 1,
+ "int.rw": 1,
+ "int.tj": 1,
+ "int.tt": 1,
+ "int.vn": 1,
+ "intelligence.museum": 1,
+ "interactive.museum": 1,
+ "intl.tn": 1,
+ "inuyama.aichi.jp": 1,
+ "inzai.chiba.jp": 1,
+ "ip6.arpa": 1,
+ "iraq.museum": 1,
+ "irc.pl": 1,
+ "iris.arpa": 1,
+ "irkutsk.ru": 1,
+ "iron.museum": 1,
+ "iruma.saitama.jp": 1,
+ "is-a-anarchist.com": 1,
+ "is-a-blogger.com": 1,
+ "is-a-bookkeeper.com": 1,
+ "is-a-bruinsfan.org": 1,
+ "is-a-bulls-fan.com": 1,
+ "is-a-candidate.org": 1,
+ "is-a-caterer.com": 1,
+ "is-a-celticsfan.org": 1,
+ "is-a-chef.com": 1,
+ "is-a-chef.net": 1,
+ "is-a-chef.org": 1,
+ "is-a-conservative.com": 1,
+ "is-a-cpa.com": 1,
+ "is-a-cubicle-slave.com": 1,
+ "is-a-democrat.com": 1,
+ "is-a-designer.com": 1,
+ "is-a-doctor.com": 1,
+ "is-a-financialadvisor.com": 1,
+ "is-a-geek.com": 1,
+ "is-a-geek.net": 1,
+ "is-a-geek.org": 1,
+ "is-a-green.com": 1,
+ "is-a-guru.com": 1,
+ "is-a-hard-worker.com": 1,
+ "is-a-hunter.com": 1,
+ "is-a-knight.org": 1,
+ "is-a-landscaper.com": 1,
+ "is-a-lawyer.com": 1,
+ "is-a-liberal.com": 1,
+ "is-a-libertarian.com": 1,
+ "is-a-linux-user.org": 1,
+ "is-a-llama.com": 1,
+ "is-a-musician.com": 1,
+ "is-a-nascarfan.com": 1,
+ "is-a-nurse.com": 1,
+ "is-a-painter.com": 1,
+ "is-a-patsfan.org": 1,
+ "is-a-personaltrainer.com": 1,
+ "is-a-photographer.com": 1,
+ "is-a-player.com": 1,
+ "is-a-republican.com": 1,
+ "is-a-rockstar.com": 1,
+ "is-a-socialist.com": 1,
+ "is-a-soxfan.org": 1,
+ "is-a-student.com": 1,
+ "is-a-teacher.com": 1,
+ "is-a-techie.com": 1,
+ "is-a-therapist.com": 1,
+ "is-an-accountant.com": 1,
+ "is-an-actor.com": 1,
+ "is-an-actress.com": 1,
+ "is-an-anarchist.com": 1,
+ "is-an-artist.com": 1,
+ "is-an-engineer.com": 1,
+ "is-an-entertainer.com": 1,
+ "is-by.us": 1,
+ "is-certified.com": 1,
+ "is-found.org": 1,
+ "is-gone.com": 1,
+ "is-into-anime.com": 1,
+ "is-into-cars.com": 1,
+ "is-into-cartoons.com": 1,
+ "is-into-games.com": 1,
+ "is-leet.com": 1,
+ "is-lost.org": 1,
+ "is-not-certified.com": 1,
+ "is-saved.org": 1,
+ "is-slick.com": 1,
+ "is-uberleet.com": 1,
+ "is-very-bad.org": 1,
+ "is-very-evil.org": 1,
+ "is-very-good.org": 1,
+ "is-very-nice.org": 1,
+ "is-very-sweet.org": 1,
+ "is-with-theband.com": 1,
+ "is.it": 1,
+ "isa-geek.com": 1,
+ "isa-geek.net": 1,
+ "isa-geek.org": 1,
+ "isa-hockeynut.com": 1,
+ "isa.kagoshima.jp": 1,
+ "isa.us": 1,
+ "isahaya.nagasaki.jp": 1,
+ "ise.mie.jp": 1,
+ "isehara.kanagawa.jp": 1,
+ "isen.kagoshima.jp": 1,
+ "isernia.it": 1,
+ "isesaki.gunma.jp": 1,
+ "ishigaki.okinawa.jp": 1,
+ "ishikari.hokkaido.jp": 1,
+ "ishikawa.fukushima.jp": 1,
+ "ishikawa.jp": 1,
+ "ishikawa.okinawa.jp": 1,
+ "ishinomaki.miyagi.jp": 1,
+ "isla.pr": 1,
+ "isleofman.museum": 1,
+ "isshiki.aichi.jp": 1,
+ "issmarterthanyou.com": 1,
+ "isteingeek.de": 1,
+ "istmein.de": 1,
+ "isumi.chiba.jp": 1,
+ "it.ao": 1,
+ "itabashi.tokyo.jp": 1,
+ "itako.ibaraki.jp": 1,
+ "itakura.gunma.jp": 1,
+ "itami.hyogo.jp": 1,
+ "itano.tokushima.jp": 1,
+ "itayanagi.aomori.jp": 1,
+ "ito.shizuoka.jp": 1,
+ "itoigawa.niigata.jp": 1,
+ "itoman.okinawa.jp": 1,
+ "its.me": 1,
+ "ivano-frankivsk.ua": 1,
+ "ivanovo.ru": 1,
+ "iveland.no": 1,
+ "ivgu.no": 1,
+ "iwade.wakayama.jp": 1,
+ "iwafune.tochigi.jp": 1,
+ "iwaizumi.iwate.jp": 1,
+ "iwaki.fukushima.jp": 1,
+ "iwakuni.yamaguchi.jp": 1,
+ "iwakura.aichi.jp": 1,
+ "iwama.ibaraki.jp": 1,
+ "iwamizawa.hokkaido.jp": 1,
+ "iwanai.hokkaido.jp": 1,
+ "iwanuma.miyagi.jp": 1,
+ "iwata.shizuoka.jp": 1,
+ "iwate.iwate.jp": 1,
+ "iwate.jp": 1,
+ "iwatsuki.saitama.jp": 1,
+ "iyo.ehime.jp": 1,
+ "iz.hr": 1,
+ "izena.okinawa.jp": 1,
+ "izhevsk.ru": 1,
+ "izu.shizuoka.jp": 1,
+ "izumi.kagoshima.jp": 1,
+ "izumi.osaka.jp": 1,
+ "izumiotsu.osaka.jp": 1,
+ "izumisano.osaka.jp": 1,
+ "izumizaki.fukushima.jp": 1,
+ "izumo.shimane.jp": 1,
+ "izumozaki.niigata.jp": 1,
+ "izunokuni.shizuoka.jp": 1,
+ "j.bg": 1,
+ "jamal.ru": 1,
+ "jamison.museum": 1,
+ "jan-mayen.no": 1,
+ "jar.ru": 1,
+ "jaworzno.pl": 1,
+ "jefferson.museum": 1,
+ "jeju.kr": 1,
+ "jelenia-gora.pl": 1,
+ "jeonbuk.kr": 1,
+ "jeonnam.kr": 1,
+ "jerusalem.museum": 1,
+ "jessheim.no": 1,
+ "jet.uk": 0,
+ "jevnaker.no": 1,
+ "jewelry.museum": 1,
+ "jewish.museum": 1,
+ "jewishart.museum": 1,
+ "jfk.museum": 1,
+ "jgora.pl": 1,
+ "jinsekikogen.hiroshima.jp": 1,
+ "jl.cn": 1,
+ "jm": 2,
+ "joboji.iwate.jp": 1,
+ "jobs.tt": 1,
+ "joetsu.niigata.jp": 1,
+ "jogasz.hu": 1,
+ "johana.toyama.jp": 1,
+ "jolster.no": 1,
+ "jondal.no": 1,
+ "jor.br": 1,
+ "jorpeland.no": 1,
+ "joshkar-ola.ru": 1,
+ "joso.ibaraki.jp": 1,
+ "journal.aero": 1,
+ "journalism.museum": 1,
+ "journalist.aero": 1,
+ "joyo.kyoto.jp": 1,
+ "jp.net": 1,
+ "jpn.com": 1,
+ "js.cn": 1,
+ "judaica.museum": 1,
+ "judygarland.museum": 1,
+ "juedisches.museum": 1,
+ "juif.museum": 1,
+ "jur.pro": 1,
+ "jus.br": 1,
+ "jx.cn": 1,
+ "j\u00f8lster.no": 1,
+ "j\u00f8rpeland.no": 1,
+ "k-uralsk.ru": 1,
+ "k.bg": 1,
+ "k.se": 1,
+ "k12.ak.us": 1,
+ "k12.al.us": 1,
+ "k12.ar.us": 1,
+ "k12.as.us": 1,
+ "k12.az.us": 1,
+ "k12.ca.us": 1,
+ "k12.co.us": 1,
+ "k12.ct.us": 1,
+ "k12.dc.us": 1,
+ "k12.de.us": 1,
+ "k12.ec": 1,
+ "k12.fl.us": 1,
+ "k12.ga.us": 1,
+ "k12.gu.us": 1,
+ "k12.ia.us": 1,
+ "k12.id.us": 1,
+ "k12.il.us": 1,
+ "k12.in.us": 1,
+ "k12.ks.us": 1,
+ "k12.ky.us": 1,
+ "k12.la.us": 1,
+ "k12.ma.us": 1,
+ "k12.md.us": 1,
+ "k12.me.us": 1,
+ "k12.mi.us": 1,
+ "k12.mn.us": 1,
+ "k12.mo.us": 1,
+ "k12.ms.us": 1,
+ "k12.mt.us": 1,
+ "k12.nc.us": 1,
+ "k12.nd.us": 1,
+ "k12.ne.us": 1,
+ "k12.nh.us": 1,
+ "k12.nj.us": 1,
+ "k12.nm.us": 1,
+ "k12.nv.us": 1,
+ "k12.ny.us": 1,
+ "k12.oh.us": 1,
+ "k12.ok.us": 1,
+ "k12.or.us": 1,
+ "k12.pa.us": 1,
+ "k12.pr.us": 1,
+ "k12.ri.us": 1,
+ "k12.sc.us": 1,
+ "k12.sd.us": 1,
+ "k12.tn.us": 1,
+ "k12.tx.us": 1,
+ "k12.ut.us": 1,
+ "k12.va.us": 1,
+ "k12.vi": 1,
+ "k12.vi.us": 1,
+ "k12.vt.us": 1,
+ "k12.wa.us": 1,
+ "k12.wi.us": 1,
+ "k12.wv.us": 1,
+ "k12.wy.us": 1,
+ "kadena.okinawa.jp": 1,
+ "kadogawa.miyazaki.jp": 1,
+ "kadoma.osaka.jp": 1,
+ "kafjord.no": 1,
+ "kaga.ishikawa.jp": 1,
+ "kagami.kochi.jp": 1,
+ "kagamiishi.fukushima.jp": 1,
+ "kagamino.okayama.jp": 1,
+ "kagawa.jp": 1,
+ "kagoshima.jp": 1,
+ "kagoshima.kagoshima.jp": 1,
+ "kaho.fukuoka.jp": 1,
+ "kahoku.ishikawa.jp": 1,
+ "kahoku.yamagata.jp": 1,
+ "kai.yamanashi.jp": 1,
+ "kainan.tokushima.jp": 1,
+ "kainan.wakayama.jp": 1,
+ "kaisei.kanagawa.jp": 1,
+ "kaita.hiroshima.jp": 1,
+ "kaizuka.osaka.jp": 1,
+ "kakamigahara.gifu.jp": 1,
+ "kakegawa.shizuoka.jp": 1,
+ "kakinoki.shimane.jp": 1,
+ "kakogawa.hyogo.jp": 1,
+ "kakuda.miyagi.jp": 1,
+ "kalisz.pl": 1,
+ "kalmykia.ru": 1,
+ "kaluga.ru": 1,
+ "kamagaya.chiba.jp": 1,
+ "kamaishi.iwate.jp": 1,
+ "kamakura.kanagawa.jp": 1,
+ "kamchatka.ru": 1,
+ "kameoka.kyoto.jp": 1,
+ "kameyama.mie.jp": 1,
+ "kami.kochi.jp": 1,
+ "kami.miyagi.jp": 1,
+ "kamiamakusa.kumamoto.jp": 1,
+ "kamifurano.hokkaido.jp": 1,
+ "kamigori.hyogo.jp": 1,
+ "kamiichi.toyama.jp": 1,
+ "kamiizumi.saitama.jp": 1,
+ "kamijima.ehime.jp": 1,
+ "kamikawa.hokkaido.jp": 1,
+ "kamikawa.hyogo.jp": 1,
+ "kamikawa.saitama.jp": 1,
+ "kamikitayama.nara.jp": 1,
+ "kamikoani.akita.jp": 1,
+ "kamimine.saga.jp": 1,
+ "kaminokawa.tochigi.jp": 1,
+ "kaminoyama.yamagata.jp": 1,
+ "kamioka.akita.jp": 1,
+ "kamisato.saitama.jp": 1,
+ "kamishihoro.hokkaido.jp": 1,
+ "kamisu.ibaraki.jp": 1,
+ "kamisunagawa.hokkaido.jp": 1,
+ "kamitonda.wakayama.jp": 1,
+ "kamitsue.oita.jp": 1,
+ "kamo.kyoto.jp": 1,
+ "kamo.niigata.jp": 1,
+ "kamoenai.hokkaido.jp": 1,
+ "kamogawa.chiba.jp": 1,
+ "kanagawa.jp": 1,
+ "kanan.osaka.jp": 1,
+ "kanazawa.ishikawa.jp": 1,
+ "kanegasaki.iwate.jp": 1,
+ "kaneyama.fukushima.jp": 1,
+ "kaneyama.yamagata.jp": 1,
+ "kani.gifu.jp": 1,
+ "kanie.aichi.jp": 1,
+ "kanmaki.nara.jp": 1,
+ "kanna.gunma.jp": 1,
+ "kannami.shizuoka.jp": 1,
+ "kanonji.kagawa.jp": 1,
+ "kanoya.kagoshima.jp": 1,
+ "kanra.gunma.jp": 1,
+ "kanuma.tochigi.jp": 1,
+ "kanzaki.saga.jp": 1,
+ "karasjohka.no": 1,
+ "karasjok.no": 1,
+ "karasuyama.tochigi.jp": 1,
+ "karate.museum": 1,
+ "karatsu.saga.jp": 1,
+ "karelia.ru": 1,
+ "karikatur.museum": 1,
+ "kariwa.niigata.jp": 1,
+ "kariya.aichi.jp": 1,
+ "karlsoy.no": 1,
+ "karmoy.no": 1,
+ "karm\u00f8y.no": 1,
+ "karpacz.pl": 1,
+ "kartuzy.pl": 1,
+ "karuizawa.nagano.jp": 1,
+ "karumai.iwate.jp": 1,
+ "kasahara.gifu.jp": 1,
+ "kasai.hyogo.jp": 1,
+ "kasama.ibaraki.jp": 1,
+ "kasamatsu.gifu.jp": 1,
+ "kasaoka.okayama.jp": 1,
+ "kashiba.nara.jp": 1,
+ "kashihara.nara.jp": 1,
+ "kashima.ibaraki.jp": 1,
+ "kashima.kumamoto.jp": 1,
+ "kashima.saga.jp": 1,
+ "kashiwa.chiba.jp": 1,
+ "kashiwara.osaka.jp": 1,
+ "kashiwazaki.niigata.jp": 1,
+ "kasuga.fukuoka.jp": 1,
+ "kasuga.hyogo.jp": 1,
+ "kasugai.aichi.jp": 1,
+ "kasukabe.saitama.jp": 1,
+ "kasumigaura.ibaraki.jp": 1,
+ "kasuya.fukuoka.jp": 1,
+ "kaszuby.pl": 1,
+ "katagami.akita.jp": 1,
+ "katano.osaka.jp": 1,
+ "katashina.gunma.jp": 1,
+ "katori.chiba.jp": 1,
+ "katowice.pl": 1,
+ "katsuragi.nara.jp": 1,
+ "katsuragi.wakayama.jp": 1,
+ "katsushika.tokyo.jp": 1,
+ "katsuura.chiba.jp": 1,
+ "katsuyama.fukui.jp": 1,
+ "kautokeino.no": 1,
+ "kawaba.gunma.jp": 1,
+ "kawachinagano.osaka.jp": 1,
+ "kawagoe.mie.jp": 1,
+ "kawagoe.saitama.jp": 1,
+ "kawaguchi.saitama.jp": 1,
+ "kawahara.tottori.jp": 1,
+ "kawai.iwate.jp": 1,
+ "kawai.nara.jp": 1,
+ "kawajima.saitama.jp": 1,
+ "kawakami.nagano.jp": 1,
+ "kawakami.nara.jp": 1,
+ "kawakita.ishikawa.jp": 1,
+ "kawamata.fukushima.jp": 1,
+ "kawaminami.miyazaki.jp": 1,
+ "kawanabe.kagoshima.jp": 1,
+ "kawanehon.shizuoka.jp": 1,
+ "kawanishi.hyogo.jp": 1,
+ "kawanishi.nara.jp": 1,
+ "kawanishi.yamagata.jp": 1,
+ "kawara.fukuoka.jp": 1,
+ "kawasaki.jp": 2,
+ "kawasaki.miyagi.jp": 1,
+ "kawatana.nagasaki.jp": 1,
+ "kawaue.gifu.jp": 1,
+ "kawazu.shizuoka.jp": 1,
+ "kayabe.hokkaido.jp": 1,
+ "kazan.ru": 1,
+ "kazimierz-dolny.pl": 1,
+ "kazo.saitama.jp": 1,
+ "kazuno.akita.jp": 1,
+ "kchr.ru": 1,
+ "ke": 2,
+ "keisen.fukuoka.jp": 1,
+ "kembuchi.hokkaido.jp": 1,
+ "kemerovo.ru": 1,
+ "kepno.pl": 1,
+ "kesennuma.miyagi.jp": 1,
+ "ketrzyn.pl": 1,
+ "kg.kr": 1,
+ "kh": 2,
+ "kh.ua": 1,
+ "khabarovsk.ru": 1,
+ "khakassia.ru": 1,
+ "kharkiv.ua": 1,
+ "kharkov.ua": 1,
+ "kherson.ua": 1,
+ "khmelnitskiy.ua": 1,
+ "khmelnytskyi.ua": 1,
+ "khv.ru": 1,
+ "kibichuo.okayama.jp": 1,
+ "kicks-ass.net": 1,
+ "kicks-ass.org": 1,
+ "kids.museum": 1,
+ "kids.us": 1,
+ "kiev.ua": 1,
+ "kiho.mie.jp": 1,
+ "kihoku.ehime.jp": 1,
+ "kijo.miyazaki.jp": 1,
+ "kikonai.hokkaido.jp": 1,
+ "kikuchi.kumamoto.jp": 1,
+ "kikugawa.shizuoka.jp": 1,
+ "kimino.wakayama.jp": 1,
+ "kimitsu.chiba.jp": 1,
+ "kimobetsu.hokkaido.jp": 1,
+ "kin.okinawa.jp": 1,
+ "kinko.kagoshima.jp": 1,
+ "kinokawa.wakayama.jp": 1,
+ "kira.aichi.jp": 1,
+ "kirkenes.no": 1,
+ "kirov.ru": 1,
+ "kirovograd.ua": 1,
+ "kiryu.gunma.jp": 1,
+ "kisarazu.chiba.jp": 1,
+ "kishiwada.osaka.jp": 1,
+ "kiso.nagano.jp": 1,
+ "kisofukushima.nagano.jp": 1,
+ "kisosaki.mie.jp": 1,
+ "kita.kyoto.jp": 1,
+ "kita.osaka.jp": 1,
+ "kita.tokyo.jp": 1,
+ "kitaaiki.nagano.jp": 1,
+ "kitaakita.akita.jp": 1,
+ "kitadaito.okinawa.jp": 1,
+ "kitagata.gifu.jp": 1,
+ "kitagata.saga.jp": 1,
+ "kitagawa.kochi.jp": 1,
+ "kitagawa.miyazaki.jp": 1,
+ "kitahata.saga.jp": 1,
+ "kitahiroshima.hokkaido.jp": 1,
+ "kitakami.iwate.jp": 1,
+ "kitakata.fukushima.jp": 1,
+ "kitakata.miyazaki.jp": 1,
+ "kitakyushu.jp": 2,
+ "kitami.hokkaido.jp": 1,
+ "kitamoto.saitama.jp": 1,
+ "kitanakagusuku.okinawa.jp": 1,
+ "kitashiobara.fukushima.jp": 1,
+ "kitaura.miyazaki.jp": 1,
+ "kitayama.wakayama.jp": 1,
+ "kiwa.mie.jp": 1,
+ "kiyama.saga.jp": 1,
+ "kiyokawa.kanagawa.jp": 1,
+ "kiyosato.hokkaido.jp": 1,
+ "kiyose.tokyo.jp": 1,
+ "kiyosu.aichi.jp": 1,
+ "kizu.kyoto.jp": 1,
+ "klabu.no": 1,
+ "klepp.no": 1,
+ "klodzko.pl": 1,
+ "kl\u00e6bu.no": 1,
+ "km.ua": 1,
+ "kms.ru": 1,
+ "knowsitall.info": 1,
+ "kobayashi.miyazaki.jp": 1,
+ "kobe.jp": 2,
+ "kobierzyce.pl": 1,
+ "kochi.jp": 1,
+ "kochi.kochi.jp": 1,
+ "kodaira.tokyo.jp": 1,
+ "koebenhavn.museum": 1,
+ "koeln.museum": 1,
+ "koenig.ru": 1,
+ "kofu.yamanashi.jp": 1,
+ "koga.fukuoka.jp": 1,
+ "koga.ibaraki.jp": 1,
+ "koganei.tokyo.jp": 1,
+ "koge.tottori.jp": 1,
+ "koka.shiga.jp": 1,
+ "kokonoe.oita.jp": 1,
+ "kokubunji.tokyo.jp": 1,
+ "kolobrzeg.pl": 1,
+ "komae.tokyo.jp": 1,
+ "komagane.nagano.jp": 1,
+ "komaki.aichi.jp": 1,
+ "komatsu.ishikawa.jp": 1,
+ "komatsushima.tokushima.jp": 1,
+ "komforb.se": 1,
+ "komi.ru": 1,
+ "kommunalforbund.se": 1,
+ "kommune.no": 1,
+ "komono.mie.jp": 1,
+ "komoro.nagano.jp": 1,
+ "komvux.se": 1,
+ "konan.aichi.jp": 1,
+ "konan.shiga.jp": 1,
+ "kongsberg.no": 1,
+ "kongsvinger.no": 1,
+ "konin.pl": 1,
+ "konskowola.pl": 1,
+ "konyvelo.hu": 1,
+ "koori.fukushima.jp": 1,
+ "kopervik.no": 1,
+ "koriyama.fukushima.jp": 1,
+ "koryo.nara.jp": 1,
+ "kosa.kumamoto.jp": 1,
+ "kosai.shizuoka.jp": 1,
+ "kosaka.akita.jp": 1,
+ "kosei.shiga.jp": 1,
+ "koshigaya.saitama.jp": 1,
+ "koshimizu.hokkaido.jp": 1,
+ "koshu.yamanashi.jp": 1,
+ "kostroma.ru": 1,
+ "kosuge.yamanashi.jp": 1,
+ "kota.aichi.jp": 1,
+ "koto.shiga.jp": 1,
+ "koto.tokyo.jp": 1,
+ "kotohira.kagawa.jp": 1,
+ "kotoura.tottori.jp": 1,
+ "kouhoku.saga.jp": 1,
+ "kounosu.saitama.jp": 1,
+ "kouyama.kagoshima.jp": 1,
+ "kouzushima.tokyo.jp": 1,
+ "koya.wakayama.jp": 1,
+ "koza.wakayama.jp": 1,
+ "kozagawa.wakayama.jp": 1,
+ "kozaki.chiba.jp": 1,
+ "kr.com": 1,
+ "kr.it": 1,
+ "kr.ua": 1,
+ "kraanghke.no": 1,
+ "kragero.no": 1,
+ "krager\u00f8.no": 1,
+ "krakow.pl": 1,
+ "krasnoyarsk.ru": 1,
+ "kristiansand.no": 1,
+ "kristiansund.no": 1,
+ "krodsherad.no": 1,
+ "krokstadelva.no": 1,
+ "krym.ua": 1,
+ "kr\u00e5anghke.no": 1,
+ "kr\u00f8dsherad.no": 1,
+ "ks.ua": 1,
+ "ks.us": 1,
+ "kuban.ru": 1,
+ "kuchinotsu.nagasaki.jp": 1,
+ "kudamatsu.yamaguchi.jp": 1,
+ "kudoyama.wakayama.jp": 1,
+ "kui.hiroshima.jp": 1,
+ "kuji.iwate.jp": 1,
+ "kuju.oita.jp": 1,
+ "kujukuri.chiba.jp": 1,
+ "kuki.saitama.jp": 1,
+ "kumagaya.saitama.jp": 1,
+ "kumakogen.ehime.jp": 1,
+ "kumamoto.jp": 1,
+ "kumamoto.kumamoto.jp": 1,
+ "kumano.hiroshima.jp": 1,
+ "kumano.mie.jp": 1,
+ "kumatori.osaka.jp": 1,
+ "kumejima.okinawa.jp": 1,
+ "kumenan.okayama.jp": 1,
+ "kumiyama.kyoto.jp": 1,
+ "kunigami.okinawa.jp": 1,
+ "kunimi.fukushima.jp": 1,
+ "kunisaki.oita.jp": 1,
+ "kunitachi.tokyo.jp": 1,
+ "kunitomi.miyazaki.jp": 1,
+ "kunneppu.hokkaido.jp": 1,
+ "kunohe.iwate.jp": 1,
+ "kunst.museum": 1,
+ "kunstsammlung.museum": 1,
+ "kunstunddesign.museum": 1,
+ "kurashiki.okayama.jp": 1,
+ "kurate.fukuoka.jp": 1,
+ "kure.hiroshima.jp": 1,
+ "kurgan.ru": 1,
+ "kuriyama.hokkaido.jp": 1,
+ "kurobe.toyama.jp": 1,
+ "kurogi.fukuoka.jp": 1,
+ "kuroishi.aomori.jp": 1,
+ "kuroiso.tochigi.jp": 1,
+ "kuromatsunai.hokkaido.jp": 1,
+ "kurotaki.nara.jp": 1,
+ "kursk.ru": 1,
+ "kurume.fukuoka.jp": 1,
+ "kusatsu.gunma.jp": 1,
+ "kusatsu.shiga.jp": 1,
+ "kushima.miyazaki.jp": 1,
+ "kushimoto.wakayama.jp": 1,
+ "kushiro.hokkaido.jp": 1,
+ "kustanai.ru": 1,
+ "kusu.oita.jp": 1,
+ "kutchan.hokkaido.jp": 1,
+ "kutno.pl": 1,
+ "kuwana.mie.jp": 1,
+ "kuzbass.ru": 1,
+ "kuzumaki.iwate.jp": 1,
+ "kv.ua": 1,
+ "kvafjord.no": 1,
+ "kvalsund.no": 1,
+ "kvam.no": 1,
+ "kvanangen.no": 1,
+ "kvinesdal.no": 1,
+ "kvinnherad.no": 1,
+ "kviteseid.no": 1,
+ "kvitsoy.no": 1,
+ "kvits\u00f8y.no": 1,
+ "kv\u00e6fjord.no": 1,
+ "kv\u00e6nangen.no": 1,
+ "kw": 2,
+ "ky.us": 1,
+ "kyiv.ua": 1,
+ "kyonan.chiba.jp": 1,
+ "kyotamba.kyoto.jp": 1,
+ "kyotanabe.kyoto.jp": 1,
+ "kyotango.kyoto.jp": 1,
+ "kyoto.jp": 1,
+ "kyowa.akita.jp": 1,
+ "kyowa.hokkaido.jp": 1,
+ "kyuragi.saga.jp": 1,
+ "k\u00e1r\u00e1\u0161johka.no": 1,
+ "k\u00e5fjord.no": 1,
+ "l.bg": 1,
+ "l.se": 1,
+ "la-spezia.it": 1,
+ "la.us": 1,
+ "laakesvuemie.no": 1,
+ "labor.museum": 1,
+ "labour.museum": 1,
+ "lahppi.no": 1,
+ "lajolla.museum": 1,
+ "lakas.hu": 1,
+ "lanbib.se": 1,
+ "lancashire.museum": 1,
+ "land-4-sale.us": 1,
+ "landes.museum": 1,
+ "langevag.no": 1,
+ "langev\u00e5g.no": 1,
+ "lans.museum": 1,
+ "lapy.pl": 1,
+ "laquila.it": 1,
+ "lardal.no": 1,
+ "larsson.museum": 1,
+ "larvik.no": 1,
+ "laspezia.it": 1,
+ "latina.it": 1,
+ "lavagis.no": 1,
+ "lavangen.no": 1,
+ "law.pro": 1,
+ "lc.it": 1,
+ "le.it": 1,
+ "leangaviika.no": 1,
+ "leasing.aero": 1,
+ "lea\u014bgaviika.no": 1,
+ "lebesby.no": 1,
+ "lebork.pl": 1,
+ "lebtimnetz.de": 1,
+ "lecce.it": 1,
+ "lecco.it": 1,
+ "leg.br": 1,
+ "legnica.pl": 1,
+ "leikanger.no": 1,
+ "leirfjord.no": 1,
+ "leirvik.no": 1,
+ "leitungsen.de": 1,
+ "leka.no": 1,
+ "leksvik.no": 1,
+ "lel.br": 1,
+ "lenvik.no": 1,
+ "lerdal.no": 1,
+ "lesja.no": 1,
+ "levanger.no": 1,
+ "lewismiller.museum": 1,
+ "lezajsk.pl": 1,
+ "lg.jp": 1,
+ "lg.ua": 1,
+ "li.it": 1,
+ "lib.ak.us": 1,
+ "lib.al.us": 1,
+ "lib.ar.us": 1,
+ "lib.as.us": 1,
+ "lib.az.us": 1,
+ "lib.ca.us": 1,
+ "lib.co.us": 1,
+ "lib.ct.us": 1,
+ "lib.dc.us": 1,
+ "lib.de.us": 1,
+ "lib.ee": 1,
+ "lib.fl.us": 1,
+ "lib.ga.us": 1,
+ "lib.gu.us": 1,
+ "lib.hi.us": 1,
+ "lib.ia.us": 1,
+ "lib.id.us": 1,
+ "lib.il.us": 1,
+ "lib.in.us": 1,
+ "lib.ks.us": 1,
+ "lib.ky.us": 1,
+ "lib.la.us": 1,
+ "lib.ma.us": 1,
+ "lib.md.us": 1,
+ "lib.me.us": 1,
+ "lib.mi.us": 1,
+ "lib.mn.us": 1,
+ "lib.mo.us": 1,
+ "lib.ms.us": 1,
+ "lib.mt.us": 1,
+ "lib.nc.us": 1,
+ "lib.nd.us": 1,
+ "lib.ne.us": 1,
+ "lib.nh.us": 1,
+ "lib.nj.us": 1,
+ "lib.nm.us": 1,
+ "lib.nv.us": 1,
+ "lib.ny.us": 1,
+ "lib.oh.us": 1,
+ "lib.ok.us": 1,
+ "lib.or.us": 1,
+ "lib.pa.us": 1,
+ "lib.pr.us": 1,
+ "lib.ri.us": 1,
+ "lib.sc.us": 1,
+ "lib.sd.us": 1,
+ "lib.tn.us": 1,
+ "lib.tx.us": 1,
+ "lib.ut.us": 1,
+ "lib.va.us": 1,
+ "lib.vi.us": 1,
+ "lib.vt.us": 1,
+ "lib.wa.us": 1,
+ "lib.wi.us": 1,
+ "lib.wv.us": 1,
+ "lib.wy.us": 1,
+ "lier.no": 1,
+ "lierne.no": 1,
+ "likes-pie.com": 1,
+ "likescandy.com": 1,
+ "lillehammer.no": 1,
+ "lillesand.no": 1,
+ "limanowa.pl": 1,
+ "lincoln.museum": 1,
+ "lindas.no": 1,
+ "lindesnes.no": 1,
+ "lind\u00e5s.no": 1,
+ "linz.museum": 1,
+ "lipetsk.ru": 1,
+ "living.museum": 1,
+ "livinghistory.museum": 1,
+ "livorno.it": 1,
+ "ln.cn": 1,
+ "lo.it": 1,
+ "loabat.no": 1,
+ "loab\u00e1t.no": 1,
+ "localhistory.museum": 1,
+ "lodi.it": 1,
+ "lodingen.no": 1,
+ "logistics.aero": 1,
+ "lom.no": 1,
+ "lomza.pl": 1,
+ "london.museum": 1,
+ "loppa.no": 1,
+ "lorenskog.no": 1,
+ "losangeles.museum": 1,
+ "loten.no": 1,
+ "louvre.museum": 1,
+ "lowicz.pl": 1,
+ "loyalist.museum": 1,
+ "lt.it": 1,
+ "lt.ua": 1,
+ "ltd.co.im": 1,
+ "ltd.gi": 1,
+ "ltd.lk": 1,
+ "lu.it": 1,
+ "lubin.pl": 1,
+ "lucca.it": 1,
+ "lucerne.museum": 1,
+ "lugansk.ua": 1,
+ "lukow.pl": 1,
+ "lund.no": 1,
+ "lunner.no": 1,
+ "luroy.no": 1,
+ "lur\u00f8y.no": 1,
+ "luster.no": 1,
+ "lutsk.ua": 1,
+ "luxembourg.museum": 1,
+ "luzern.museum": 1,
+ "lv.ua": 1,
+ "lviv.ua": 1,
+ "lyngdal.no": 1,
+ "lyngen.no": 1,
+ "l\u00e1hppi.no": 1,
+ "l\u00e4ns.museum": 1,
+ "l\u00e6rdal.no": 1,
+ "l\u00f8dingen.no": 1,
+ "l\u00f8renskog.no": 1,
+ "l\u00f8ten.no": 1,
+ "m.bg": 1,
+ "m.se": 1,
+ "ma.us": 1,
+ "macerata.it": 1,
+ "machida.tokyo.jp": 1,
+ "mad.museum": 1,
+ "madrid.museum": 1,
+ "maebashi.gunma.jp": 1,
+ "magadan.ru": 1,
+ "magazine.aero": 1,
+ "magnitka.ru": 1,
+ "maibara.shiga.jp": 1,
+ "mail.pl": 1,
+ "maintenance.aero": 1,
+ "maizuru.kyoto.jp": 1,
+ "makinohara.shizuoka.jp": 1,
+ "makurazaki.kagoshima.jp": 1,
+ "malatvuopmi.no": 1,
+ "malbork.pl": 1,
+ "mallorca.museum": 1,
+ "malopolska.pl": 1,
+ "malselv.no": 1,
+ "malvik.no": 1,
+ "mamurogawa.yamagata.jp": 1,
+ "manchester.museum": 1,
+ "mandal.no": 1,
+ "maniwa.okayama.jp": 1,
+ "manno.kagawa.jp": 1,
+ "mansion.museum": 1,
+ "mansions.museum": 1,
+ "mantova.it": 1,
+ "manx.museum": 1,
+ "marburg.museum": 1,
+ "mari-el.ru": 1,
+ "mari.ru": 1,
+ "marine.ru": 1,
+ "maritime.museum": 1,
+ "maritimo.museum": 1,
+ "marker.no": 1,
+ "marketplace.aero": 1,
+ "marnardal.no": 1,
+ "marugame.kagawa.jp": 1,
+ "marumori.miyagi.jp": 1,
+ "maryland.museum": 1,
+ "marylhurst.museum": 1,
+ "masaki.ehime.jp": 1,
+ "masfjorden.no": 1,
+ "mashike.hokkaido.jp": 1,
+ "mashiki.kumamoto.jp": 1,
+ "mashiko.tochigi.jp": 1,
+ "masoy.no": 1,
+ "massa-carrara.it": 1,
+ "massacarrara.it": 1,
+ "masuda.shimane.jp": 1,
+ "mat.br": 1,
+ "matera.it": 1,
+ "matsubara.osaka.jp": 1,
+ "matsubushi.saitama.jp": 1,
+ "matsuda.kanagawa.jp": 1,
+ "matsudo.chiba.jp": 1,
+ "matsue.shimane.jp": 1,
+ "matsukawa.nagano.jp": 1,
+ "matsumae.hokkaido.jp": 1,
+ "matsumoto.kagoshima.jp": 1,
+ "matsumoto.nagano.jp": 1,
+ "matsuno.ehime.jp": 1,
+ "matsusaka.mie.jp": 1,
+ "matsushige.tokushima.jp": 1,
+ "matsushima.miyagi.jp": 1,
+ "matsuura.nagasaki.jp": 1,
+ "matsuyama.ehime.jp": 1,
+ "matsuzaki.shizuoka.jp": 1,
+ "matta-varjjat.no": 1,
+ "mazowsze.pl": 1,
+ "mazury.pl": 1,
+ "mb.ca": 1,
+ "mb.it": 1,
+ "mbone.pl": 1,
+ "mc.it": 1,
+ "md.ci": 1,
+ "md.us": 1,
+ "me.it": 1,
+ "me.us": 1,
+ "mecon.ar": 0,
+ "med.br": 1,
+ "med.ec": 1,
+ "med.ee": 1,
+ "med.ht": 1,
+ "med.ly": 1,
+ "med.pa": 1,
+ "med.pl": 1,
+ "med.pro": 1,
+ "med.sa": 1,
+ "med.sd": 1,
+ "medecin.fr": 1,
+ "medecin.km": 1,
+ "media.aero": 1,
+ "media.hu": 1,
+ "media.museum": 1,
+ "media.pl": 1,
+ "mediaphone.om": 0,
+ "medical.museum": 1,
+ "medio-campidano.it": 1,
+ "mediocampidano.it": 1,
+ "medizinhistorisches.museum": 1,
+ "meeres.museum": 1,
+ "meguro.tokyo.jp": 1,
+ "meiwa.gunma.jp": 1,
+ "meiwa.mie.jp": 1,
+ "meland.no": 1,
+ "meldal.no": 1,
+ "melhus.no": 1,
+ "meloy.no": 1,
+ "mel\u00f8y.no": 1,
+ "memorial.museum": 1,
+ "meraker.no": 1,
+ "merseine.nu": 1,
+ "mer\u00e5ker.no": 1,
+ "mesaverde.museum": 1,
+ "messina.it": 1,
+ "mi.it": 1,
+ "mi.th": 1,
+ "mi.us": 1,
+ "miasa.nagano.jp": 1,
+ "miasta.pl": 1,
+ "mibu.tochigi.jp": 1,
+ "michigan.museum": 1,
+ "microlight.aero": 1,
+ "midatlantic.museum": 1,
+ "midori.chiba.jp": 1,
+ "midori.gunma.jp": 1,
+ "midsund.no": 1,
+ "midtre-gauldal.no": 1,
+ "mie.jp": 1,
+ "mielec.pl": 1,
+ "mielno.pl": 1,
+ "mifune.kumamoto.jp": 1,
+ "mihama.aichi.jp": 1,
+ "mihama.chiba.jp": 1,
+ "mihama.fukui.jp": 1,
+ "mihama.mie.jp": 1,
+ "mihama.wakayama.jp": 1,
+ "mihara.hiroshima.jp": 1,
+ "mihara.kochi.jp": 1,
+ "miharu.fukushima.jp": 1,
+ "miho.ibaraki.jp": 1,
+ "mikasa.hokkaido.jp": 1,
+ "mikawa.yamagata.jp": 1,
+ "miki.hyogo.jp": 1,
+ "mil.ac": 1,
+ "mil.ae": 1,
+ "mil.al": 1,
+ "mil.az": 1,
+ "mil.ba": 1,
+ "mil.bo": 1,
+ "mil.br": 1,
+ "mil.by": 1,
+ "mil.cl": 1,
+ "mil.cn": 1,
+ "mil.co": 1,
+ "mil.do": 1,
+ "mil.ec": 1,
+ "mil.eg": 1,
+ "mil.ge": 1,
+ "mil.gh": 1,
+ "mil.hn": 1,
+ "mil.id": 1,
+ "mil.in": 1,
+ "mil.iq": 1,
+ "mil.jo": 1,
+ "mil.kg": 1,
+ "mil.km": 1,
+ "mil.kr": 1,
+ "mil.kz": 1,
+ "mil.lv": 1,
+ "mil.mg": 1,
+ "mil.mv": 1,
+ "mil.my": 1,
+ "mil.no": 1,
+ "mil.pe": 1,
+ "mil.ph": 1,
+ "mil.pl": 1,
+ "mil.qa": 1,
+ "mil.ru": 1,
+ "mil.rw": 1,
+ "mil.sh": 1,
+ "mil.st": 1,
+ "mil.sy": 1,
+ "mil.tj": 1,
+ "mil.tm": 1,
+ "mil.to": 1,
+ "mil.tw": 1,
+ "mil.tz": 1,
+ "mil.uy": 1,
+ "mil.vc": 1,
+ "mil.ve": 1,
+ "milan.it": 1,
+ "milano.it": 1,
+ "military.museum": 1,
+ "mill.museum": 1,
+ "mima.tokushima.jp": 1,
+ "mimata.miyazaki.jp": 1,
+ "minakami.gunma.jp": 1,
+ "minamata.kumamoto.jp": 1,
+ "minami-alps.yamanashi.jp": 1,
+ "minami.fukuoka.jp": 1,
+ "minami.kyoto.jp": 1,
+ "minami.tokushima.jp": 1,
+ "minamiaiki.nagano.jp": 1,
+ "minamiashigara.kanagawa.jp": 1,
+ "minamiawaji.hyogo.jp": 1,
+ "minamiboso.chiba.jp": 1,
+ "minamidaito.okinawa.jp": 1,
+ "minamiechizen.fukui.jp": 1,
+ "minamifurano.hokkaido.jp": 1,
+ "minamiise.mie.jp": 1,
+ "minamiizu.shizuoka.jp": 1,
+ "minamimaki.nagano.jp": 1,
+ "minamiminowa.nagano.jp": 1,
+ "minamioguni.kumamoto.jp": 1,
+ "minamisanriku.miyagi.jp": 1,
+ "minamitane.kagoshima.jp": 1,
+ "minamiuonuma.niigata.jp": 1,
+ "minamiyamashiro.kyoto.jp": 1,
+ "minano.saitama.jp": 1,
+ "minato.osaka.jp": 1,
+ "minato.tokyo.jp": 1,
+ "mincom.tn": 1,
+ "mine.nu": 1,
+ "miners.museum": 1,
+ "mining.museum": 1,
+ "minnesota.museum": 1,
+ "mino.gifu.jp": 1,
+ "minobu.yamanashi.jp": 1,
+ "minoh.osaka.jp": 1,
+ "minokamo.gifu.jp": 1,
+ "minowa.nagano.jp": 1,
+ "misaki.okayama.jp": 1,
+ "misaki.osaka.jp": 1,
+ "misasa.tottori.jp": 1,
+ "misato.akita.jp": 1,
+ "misato.miyagi.jp": 1,
+ "misato.saitama.jp": 1,
+ "misato.shimane.jp": 1,
+ "misato.wakayama.jp": 1,
+ "misawa.aomori.jp": 1,
+ "misconfused.org": 1,
+ "mishima.fukushima.jp": 1,
+ "mishima.shizuoka.jp": 1,
+ "missile.museum": 1,
+ "missoula.museum": 1,
+ "misugi.mie.jp": 1,
+ "mitaka.tokyo.jp": 1,
+ "mitake.gifu.jp": 1,
+ "mitane.akita.jp": 1,
+ "mito.ibaraki.jp": 1,
+ "mitou.yamaguchi.jp": 1,
+ "mitoyo.kagawa.jp": 1,
+ "mitsue.nara.jp": 1,
+ "mitsuke.niigata.jp": 1,
+ "miura.kanagawa.jp": 1,
+ "miyada.nagano.jp": 1,
+ "miyagi.jp": 1,
+ "miyake.nara.jp": 1,
+ "miyako.fukuoka.jp": 1,
+ "miyako.iwate.jp": 1,
+ "miyakonojo.miyazaki.jp": 1,
+ "miyama.fukuoka.jp": 1,
+ "miyama.mie.jp": 1,
+ "miyashiro.saitama.jp": 1,
+ "miyawaka.fukuoka.jp": 1,
+ "miyazaki.jp": 1,
+ "miyazaki.miyazaki.jp": 1,
+ "miyazu.kyoto.jp": 1,
+ "miyoshi.aichi.jp": 1,
+ "miyoshi.hiroshima.jp": 1,
+ "miyoshi.saitama.jp": 1,
+ "miyoshi.tokushima.jp": 1,
+ "miyota.nagano.jp": 1,
+ "mizuho.tokyo.jp": 1,
+ "mizumaki.fukuoka.jp": 1,
+ "mizunami.gifu.jp": 1,
+ "mizusawa.iwate.jp": 1,
+ "mjondalen.no": 1,
+ "mj\u00f8ndalen.no": 1,
+ "mk.ua": 1,
+ "mm": 2,
+ "mn.it": 1,
+ "mn.us": 1,
+ "mo-i-rana.no": 1,
+ "mo.cn": 1,
+ "mo.it": 1,
+ "mo.us": 1,
+ "moareke.no": 1,
+ "mobara.chiba.jp": 1,
+ "mobi.gp": 1,
+ "mobi.na": 1,
+ "mobi.tt": 1,
+ "mochizuki.nagano.jp": 1,
+ "mod.gi": 1,
+ "mod.uk": 0,
+ "modalen.no": 1,
+ "modelling.aero": 1,
+ "modena.it": 1,
+ "modern.museum": 1,
+ "modum.no": 1,
+ "moka.tochigi.jp": 1,
+ "molde.no": 1,
+ "moma.museum": 1,
+ "mombetsu.hokkaido.jp": 1,
+ "money.museum": 1,
+ "monmouth.museum": 1,
+ "monticello.museum": 1,
+ "montreal.museum": 1,
+ "monza-brianza.it": 1,
+ "monza-e-della-brianza.it": 1,
+ "monza.it": 1,
+ "monzabrianza.it": 1,
+ "monzaebrianza.it": 1,
+ "monzaedellabrianza.it": 1,
+ "mordovia.ru": 1,
+ "moriguchi.osaka.jp": 1,
+ "morimachi.shizuoka.jp": 1,
+ "morioka.iwate.jp": 1,
+ "moriya.ibaraki.jp": 1,
+ "moriyama.shiga.jp": 1,
+ "moriyoshi.akita.jp": 1,
+ "morotsuka.miyazaki.jp": 1,
+ "moroyama.saitama.jp": 1,
+ "moscow.museum": 1,
+ "moseushi.hokkaido.jp": 1,
+ "mosjoen.no": 1,
+ "mosj\u00f8en.no": 1,
+ "moskenes.no": 1,
+ "mosreg.ru": 1,
+ "moss.no": 1,
+ "mosvik.no": 1,
+ "motegi.tochigi.jp": 1,
+ "motobu.okinawa.jp": 1,
+ "motorcycle.museum": 1,
+ "motosu.gifu.jp": 1,
+ "motoyama.kochi.jp": 1,
+ "mo\u00e5reke.no": 1,
+ "mr.no": 1,
+ "mragowo.pl": 1,
+ "ms.it": 1,
+ "ms.kr": 1,
+ "ms.us": 1,
+ "msk.ru": 1,
+ "mt": 2,
+ "mt.it": 1,
+ "mt.us": 1,
+ "muenchen.museum": 1,
+ "muenster.museum": 1,
+ "mugi.tokushima.jp": 1,
+ "muika.niigata.jp": 1,
+ "mukawa.hokkaido.jp": 1,
+ "muko.kyoto.jp": 1,
+ "mulhouse.museum": 1,
+ "munakata.fukuoka.jp": 1,
+ "muncie.museum": 1,
+ "muosat.no": 1,
+ "muos\u00e1t.no": 1,
+ "murakami.niigata.jp": 1,
+ "murata.miyagi.jp": 1,
+ "murayama.yamagata.jp": 1,
+ "murmansk.ru": 1,
+ "muroran.hokkaido.jp": 1,
+ "muroto.kochi.jp": 1,
+ "mus.br": 1,
+ "musashimurayama.tokyo.jp": 1,
+ "musashino.tokyo.jp": 1,
+ "museet.museum": 1,
+ "museum.mv": 1,
+ "museum.mw": 1,
+ "museum.no": 1,
+ "museum.tt": 1,
+ "museumcenter.museum": 1,
+ "museumvereniging.museum": 1,
+ "music.museum": 1,
+ "mutsu.aomori.jp": 1,
+ "mutsuzawa.chiba.jp": 1,
+ "mx.na": 1,
+ "mykolaiv.ua": 1,
+ "myoko.niigata.jp": 1,
+ "mypets.ws": 1,
+ "myphotos.cc": 1,
+ "mytis.ru": 1,
+ "mz": 2,
+ "m\u00e1latvuopmi.no": 1,
+ "m\u00e1tta-v\u00e1rjjat.no": 1,
+ "m\u00e5lselv.no": 1,
+ "m\u00e5s\u00f8y.no": 1,
+ "n.bg": 1,
+ "n.se": 1,
+ "na.it": 1,
+ "naamesjevuemie.no": 1,
+ "nabari.mie.jp": 1,
+ "nachikatsuura.wakayama.jp": 1,
+ "nacion.ar": 0,
+ "nagahama.shiga.jp": 1,
+ "nagai.yamagata.jp": 1,
+ "nagakute.aichi.jp": 1,
+ "nagano.jp": 1,
+ "nagano.nagano.jp": 1,
+ "naganohara.gunma.jp": 1,
+ "nagaoka.niigata.jp": 1,
+ "nagaokakyo.kyoto.jp": 1,
+ "nagara.chiba.jp": 1,
+ "nagareyama.chiba.jp": 1,
+ "nagasaki.jp": 1,
+ "nagasaki.nagasaki.jp": 1,
+ "nagasu.kumamoto.jp": 1,
+ "nagato.yamaguchi.jp": 1,
+ "nagatoro.saitama.jp": 1,
+ "nagawa.nagano.jp": 1,
+ "nagi.okayama.jp": 1,
+ "nagiso.nagano.jp": 1,
+ "nago.okinawa.jp": 1,
+ "nagoya.jp": 2,
+ "naha.okinawa.jp": 1,
+ "nahari.kochi.jp": 1,
+ "naie.hokkaido.jp": 1,
+ "naka.hiroshima.jp": 1,
+ "naka.ibaraki.jp": 1,
+ "nakadomari.aomori.jp": 1,
+ "nakagawa.fukuoka.jp": 1,
+ "nakagawa.hokkaido.jp": 1,
+ "nakagawa.nagano.jp": 1,
+ "nakagawa.tokushima.jp": 1,
+ "nakagusuku.okinawa.jp": 1,
+ "nakagyo.kyoto.jp": 1,
+ "nakai.kanagawa.jp": 1,
+ "nakama.fukuoka.jp": 1,
+ "nakamichi.yamanashi.jp": 1,
+ "nakamura.kochi.jp": 1,
+ "nakaniikawa.toyama.jp": 1,
+ "nakano.nagano.jp": 1,
+ "nakano.tokyo.jp": 1,
+ "nakanojo.gunma.jp": 1,
+ "nakanoto.ishikawa.jp": 1,
+ "nakasatsunai.hokkaido.jp": 1,
+ "nakatane.kagoshima.jp": 1,
+ "nakatombetsu.hokkaido.jp": 1,
+ "nakatsugawa.gifu.jp": 1,
+ "nakayama.yamagata.jp": 1,
+ "nakhodka.ru": 1,
+ "nakijin.okinawa.jp": 1,
+ "naklo.pl": 1,
+ "nalchik.ru": 1,
+ "namdalseid.no": 1,
+ "name.az": 1,
+ "name.eg": 1,
+ "name.hr": 1,
+ "name.jo": 1,
+ "name.mk": 1,
+ "name.mv": 1,
+ "name.my": 1,
+ "name.na": 1,
+ "name.pr": 1,
+ "name.qa": 1,
+ "name.tj": 1,
+ "name.tt": 1,
+ "name.vn": 1,
+ "namegata.ibaraki.jp": 1,
+ "namegawa.saitama.jp": 1,
+ "namerikawa.toyama.jp": 1,
+ "namie.fukushima.jp": 1,
+ "namikata.ehime.jp": 1,
+ "namsos.no": 1,
+ "namsskogan.no": 1,
+ "nanae.hokkaido.jp": 1,
+ "nanao.ishikawa.jp": 1,
+ "nanbu.tottori.jp": 1,
+ "nanbu.yamanashi.jp": 1,
+ "nango.fukushima.jp": 1,
+ "nanjo.okinawa.jp": 1,
+ "nankoku.kochi.jp": 1,
+ "nanmoku.gunma.jp": 1,
+ "nannestad.no": 1,
+ "nanporo.hokkaido.jp": 1,
+ "nantan.kyoto.jp": 1,
+ "nanto.toyama.jp": 1,
+ "nanyo.yamagata.jp": 1,
+ "naoshima.kagawa.jp": 1,
+ "naples.it": 1,
+ "napoli.it": 1,
+ "nara.jp": 1,
+ "nara.nara.jp": 1,
+ "narashino.chiba.jp": 1,
+ "narita.chiba.jp": 1,
+ "naroy.no": 1,
+ "narusawa.yamanashi.jp": 1,
+ "naruto.tokushima.jp": 1,
+ "narviika.no": 1,
+ "narvik.no": 1,
+ "nasu.tochigi.jp": 1,
+ "nasushiobara.tochigi.jp": 1,
+ "nat.tn": 1,
+ "national-library-scotland.uk": 0,
+ "national.museum": 1,
+ "nationalfirearms.museum": 1,
+ "nationalheritage.museum": 1,
+ "nativeamerican.museum": 1,
+ "natori.miyagi.jp": 1,
+ "naturalhistory.museum": 1,
+ "naturalhistorymuseum.museum": 1,
+ "naturalsciences.museum": 1,
+ "naturbruksgymn.se": 1,
+ "nature.museum": 1,
+ "naturhistorisches.museum": 1,
+ "natuurwetenschappen.museum": 1,
+ "naumburg.museum": 1,
+ "naustdal.no": 1,
+ "naval.museum": 1,
+ "navigation.aero": 1,
+ "navuotna.no": 1,
+ "nawras.om": 0,
+ "nawrastelecom.om": 0,
+ "nayoro.hokkaido.jp": 1,
+ "nb.ca": 1,
+ "nc.us": 1,
+ "nd.us": 1,
+ "ne.jp": 1,
+ "ne.kr": 1,
+ "ne.pw": 1,
+ "ne.tz": 1,
+ "ne.ug": 1,
+ "ne.us": 1,
+ "neat-url.com": 1,
+ "nebraska.museum": 1,
+ "nedre-eiker.no": 1,
+ "nel.uk": 0,
+ "nemuro.hokkaido.jp": 1,
+ "nerima.tokyo.jp": 1,
+ "nes.akershus.no": 1,
+ "nes.buskerud.no": 1,
+ "nesna.no": 1,
+ "nesodden.no": 1,
+ "nesoddtangen.no": 1,
+ "nesseby.no": 1,
+ "nesset.no": 1,
+ "net.ac": 1,
+ "net.ae": 1,
+ "net.af": 1,
+ "net.ag": 1,
+ "net.ai": 1,
+ "net.al": 1,
+ "net.an": 1,
+ "net.au": 1,
+ "net.az": 1,
+ "net.ba": 1,
+ "net.bb": 1,
+ "net.bh": 1,
+ "net.bm": 1,
+ "net.bo": 1,
+ "net.br": 1,
+ "net.bs": 1,
+ "net.bt": 1,
+ "net.bz": 1,
+ "net.ci": 1,
+ "net.cn": 1,
+ "net.co": 1,
+ "net.cu": 1,
+ "net.dm": 1,
+ "net.do": 1,
+ "net.dz": 1,
+ "net.ec": 1,
+ "net.eg": 1,
+ "net.ge": 1,
+ "net.gg": 1,
+ "net.gn": 1,
+ "net.gp": 1,
+ "net.gr": 1,
+ "net.gy": 1,
+ "net.hk": 1,
+ "net.hn": 1,
+ "net.ht": 1,
+ "net.id": 1,
+ "net.im": 1,
+ "net.in": 1,
+ "net.iq": 1,
+ "net.ir": 1,
+ "net.is": 1,
+ "net.je": 1,
+ "net.jo": 1,
+ "net.kg": 1,
+ "net.ki": 1,
+ "net.kn": 1,
+ "net.ky": 1,
+ "net.kz": 1,
+ "net.la": 1,
+ "net.lb": 1,
+ "net.lc": 1,
+ "net.lk": 1,
+ "net.lr": 1,
+ "net.lv": 1,
+ "net.ly": 1,
+ "net.ma": 1,
+ "net.me": 1,
+ "net.mk": 1,
+ "net.ml": 1,
+ "net.mo": 1,
+ "net.mu": 1,
+ "net.mv": 1,
+ "net.mw": 1,
+ "net.mx": 1,
+ "net.my": 1,
+ "net.nf": 1,
+ "net.ng": 1,
+ "net.nr": 1,
+ "net.pa": 1,
+ "net.pe": 1,
+ "net.ph": 1,
+ "net.pk": 1,
+ "net.pl": 1,
+ "net.pn": 1,
+ "net.pr": 1,
+ "net.ps": 1,
+ "net.pt": 1,
+ "net.qa": 1,
+ "net.ru": 1,
+ "net.rw": 1,
+ "net.sa": 1,
+ "net.sb": 1,
+ "net.sc": 1,
+ "net.sd": 1,
+ "net.sg": 1,
+ "net.sh": 1,
+ "net.sl": 1,
+ "net.so": 1,
+ "net.st": 1,
+ "net.sy": 1,
+ "net.th": 1,
+ "net.tj": 1,
+ "net.tm": 1,
+ "net.tn": 1,
+ "net.to": 1,
+ "net.tt": 1,
+ "net.tw": 1,
+ "net.ua": 1,
+ "net.uy": 1,
+ "net.uz": 1,
+ "net.vc": 1,
+ "net.ve": 1,
+ "net.vi": 1,
+ "net.vn": 1,
+ "net.ws": 1,
+ "neues.museum": 1,
+ "newhampshire.museum": 1,
+ "newjersey.museum": 1,
+ "newmexico.museum": 1,
+ "newport.museum": 1,
+ "news.hu": 1,
+ "newspaper.museum": 1,
+ "newyork.museum": 1,
+ "neyagawa.osaka.jp": 1,
+ "nf.ca": 1,
+ "ngo.lk": 1,
+ "ngo.ph": 1,
+ "ngo.pl": 1,
+ "nh.us": 1,
+ "nhs.uk": 2,
+ "ni": 2,
+ "nic.ar": 0,
+ "nic.im": 1,
+ "nic.in": 1,
+ "nic.tj": 1,
+ "nic.tr": 0,
+ "nic.uk": 0,
+ "nichinan.miyazaki.jp": 1,
+ "nichinan.tottori.jp": 1,
+ "niepce.museum": 1,
+ "nieruchomosci.pl": 1,
+ "niigata.jp": 1,
+ "niigata.niigata.jp": 1,
+ "niihama.ehime.jp": 1,
+ "niikappu.hokkaido.jp": 1,
+ "niimi.okayama.jp": 1,
+ "niiza.saitama.jp": 1,
+ "nikaho.akita.jp": 1,
+ "niki.hokkaido.jp": 1,
+ "nikko.tochigi.jp": 1,
+ "nikolaev.ua": 1,
+ "ninohe.iwate.jp": 1,
+ "ninomiya.kanagawa.jp": 1,
+ "nirasaki.yamanashi.jp": 1,
+ "nishi.fukuoka.jp": 1,
+ "nishi.osaka.jp": 1,
+ "nishiaizu.fukushima.jp": 1,
+ "nishiarita.saga.jp": 1,
+ "nishiawakura.okayama.jp": 1,
+ "nishiazai.shiga.jp": 1,
+ "nishigo.fukushima.jp": 1,
+ "nishihara.kumamoto.jp": 1,
+ "nishihara.okinawa.jp": 1,
+ "nishiizu.shizuoka.jp": 1,
+ "nishikata.tochigi.jp": 1,
+ "nishikatsura.yamanashi.jp": 1,
+ "nishikawa.yamagata.jp": 1,
+ "nishimera.miyazaki.jp": 1,
+ "nishinomiya.hyogo.jp": 1,
+ "nishinoomote.kagoshima.jp": 1,
+ "nishinoshima.shimane.jp": 1,
+ "nishio.aichi.jp": 1,
+ "nishiokoppe.hokkaido.jp": 1,
+ "nishitosa.kochi.jp": 1,
+ "nishiwaki.hyogo.jp": 1,
+ "nissedal.no": 1,
+ "nisshin.aichi.jp": 1,
+ "nittedal.no": 1,
+ "niyodogawa.kochi.jp": 1,
+ "nj.us": 1,
+ "nkz.ru": 1,
+ "nl.ca": 1,
+ "nl.no": 1,
+ "nls.uk": 0,
+ "nm.cn": 1,
+ "nm.us": 1,
+ "nnov.ru": 1,
+ "no.com": 1,
+ "no.it": 1,
+ "nobeoka.miyazaki.jp": 1,
+ "noboribetsu.hokkaido.jp": 1,
+ "noda.chiba.jp": 1,
+ "noda.iwate.jp": 1,
+ "nogata.fukuoka.jp": 1,
+ "nogi.tochigi.jp": 1,
+ "noheji.aomori.jp": 1,
+ "nom.ad": 1,
+ "nom.ag": 1,
+ "nom.br": 1,
+ "nom.co": 1,
+ "nom.es": 1,
+ "nom.fr": 1,
+ "nom.km": 1,
+ "nom.mg": 1,
+ "nom.pa": 1,
+ "nom.pe": 1,
+ "nom.pl": 1,
+ "nom.re": 1,
+ "nom.ro": 1,
+ "nom.tm": 1,
+ "nome.pt": 1,
+ "nomi.ishikawa.jp": 1,
+ "nonoichi.ishikawa.jp": 1,
+ "nord-aurdal.no": 1,
+ "nord-fron.no": 1,
+ "nord-odal.no": 1,
+ "norddal.no": 1,
+ "nordkapp.no": 1,
+ "nordre-land.no": 1,
+ "nordreisa.no": 1,
+ "nore-og-uvdal.no": 1,
+ "norfolk.museum": 1,
+ "norilsk.ru": 1,
+ "north.museum": 1,
+ "nose.osaka.jp": 1,
+ "nosegawa.nara.jp": 1,
+ "noshiro.akita.jp": 1,
+ "not.br": 1,
+ "notaires.fr": 1,
+ "notaires.km": 1,
+ "noto.ishikawa.jp": 1,
+ "notodden.no": 1,
+ "notogawa.shiga.jp": 1,
+ "notteroy.no": 1,
+ "nov.ru": 1,
+ "novara.it": 1,
+ "novosibirsk.ru": 1,
+ "nowaruda.pl": 1,
+ "nozawaonsen.nagano.jp": 1,
+ "np": 2,
+ "nrw.museum": 1,
+ "ns.ca": 1,
+ "nsk.ru": 1,
+ "nsn.us": 1,
+ "nsw.au": 1,
+ "nsw.edu.au": 1,
+ "nt.au": 1,
+ "nt.ca": 1,
+ "nt.edu.au": 1,
+ "nt.gov.au": 1,
+ "nt.no": 1,
+ "nt.ro": 1,
+ "ntr.br": 1,
+ "nu.ca": 1,
+ "nu.it": 1,
+ "nuernberg.museum": 1,
+ "numata.gunma.jp": 1,
+ "numata.hokkaido.jp": 1,
+ "numazu.shizuoka.jp": 1,
+ "nuoro.it": 1,
+ "nuremberg.museum": 1,
+ "nv.us": 1,
+ "nx.cn": 1,
+ "ny.us": 1,
+ "nyc.museum": 1,
+ "nyny.museum": 1,
+ "nysa.pl": 1,
+ "nyuzen.toyama.jp": 1,
+ "nz": 2,
+ "n\u00e1vuotna.no": 1,
+ "n\u00e5\u00e5mesjevuemie.no": 1,
+ "n\u00e6r\u00f8y.no": 1,
+ "n\u00f8tter\u00f8y.no": 1,
+ "o.bg": 1,
+ "o.se": 1,
+ "oamishirasato.chiba.jp": 1,
+ "oarai.ibaraki.jp": 1,
+ "obama.fukui.jp": 1,
+ "obama.nagasaki.jp": 1,
+ "obanazawa.yamagata.jp": 1,
+ "obihiro.hokkaido.jp": 1,
+ "obira.hokkaido.jp": 1,
+ "obu.aichi.jp": 1,
+ "obuse.nagano.jp": 1,
+ "oceanographic.museum": 1,
+ "oceanographique.museum": 1,
+ "ochi.kochi.jp": 1,
+ "od.ua": 1,
+ "odate.akita.jp": 1,
+ "odawara.kanagawa.jp": 1,
+ "odda.no": 1,
+ "odesa.ua": 1,
+ "odessa.ua": 1,
+ "odo.br": 1,
+ "oe.yamagata.jp": 1,
+ "of.by": 1,
+ "of.no": 1,
+ "off.ai": 1,
+ "office-on-the.net": 1,
+ "ofunato.iwate.jp": 1,
+ "og.ao": 1,
+ "og.it": 1,
+ "oga.akita.jp": 1,
+ "ogaki.gifu.jp": 1,
+ "ogano.saitama.jp": 1,
+ "ogasawara.tokyo.jp": 1,
+ "ogata.akita.jp": 1,
+ "ogawa.ibaraki.jp": 1,
+ "ogawa.nagano.jp": 1,
+ "ogawa.saitama.jp": 1,
+ "ogawara.miyagi.jp": 1,
+ "ogi.saga.jp": 1,
+ "ogimi.okinawa.jp": 1,
+ "ogliastra.it": 1,
+ "ogori.fukuoka.jp": 1,
+ "ogose.saitama.jp": 1,
+ "oguchi.aichi.jp": 1,
+ "oguni.kumamoto.jp": 1,
+ "oguni.yamagata.jp": 1,
+ "oh.us": 1,
+ "oharu.aichi.jp": 1,
+ "ohda.shimane.jp": 1,
+ "ohi.fukui.jp": 1,
+ "ohira.miyagi.jp": 1,
+ "ohira.tochigi.jp": 1,
+ "ohkura.yamagata.jp": 1,
+ "ohtawara.tochigi.jp": 1,
+ "oi.kanagawa.jp": 1,
+ "oirase.aomori.jp": 1,
+ "oishida.yamagata.jp": 1,
+ "oiso.kanagawa.jp": 1,
+ "oita.jp": 1,
+ "oita.oita.jp": 1,
+ "oizumi.gunma.jp": 1,
+ "oji.nara.jp": 1,
+ "ojiya.niigata.jp": 1,
+ "ok.us": 1,
+ "okagaki.fukuoka.jp": 1,
+ "okawa.fukuoka.jp": 1,
+ "okawa.kochi.jp": 1,
+ "okaya.nagano.jp": 1,
+ "okayama.jp": 1,
+ "okayama.okayama.jp": 1,
+ "okazaki.aichi.jp": 1,
+ "okegawa.saitama.jp": 1,
+ "oketo.hokkaido.jp": 1,
+ "oki.fukuoka.jp": 1,
+ "okinawa.jp": 1,
+ "okinawa.okinawa.jp": 1,
+ "okinoshima.shimane.jp": 1,
+ "okoppe.hokkaido.jp": 1,
+ "oksnes.no": 1,
+ "okuizumo.shimane.jp": 1,
+ "okuma.fukushima.jp": 1,
+ "okutama.tokyo.jp": 1,
+ "ol.no": 1,
+ "olawa.pl": 1,
+ "olbia-tempio.it": 1,
+ "olbiatempio.it": 1,
+ "olecko.pl": 1,
+ "olkusz.pl": 1,
+ "olsztyn.pl": 1,
+ "om": 2,
+ "omachi.nagano.jp": 1,
+ "omachi.saga.jp": 1,
+ "omaezaki.shizuoka.jp": 1,
+ "omaha.museum": 1,
+ "omanmobile.om": 0,
+ "omanpost.om": 0,
+ "omantel.om": 0,
+ "omasvuotna.no": 1,
+ "ome.tokyo.jp": 1,
+ "omi.nagano.jp": 1,
+ "omi.niigata.jp": 1,
+ "omigawa.chiba.jp": 1,
+ "omihachiman.shiga.jp": 1,
+ "omitama.ibaraki.jp": 1,
+ "omiya.saitama.jp": 1,
+ "omotego.fukushima.jp": 1,
+ "omsk.ru": 1,
+ "omura.nagasaki.jp": 1,
+ "omuta.fukuoka.jp": 1,
+ "on-the-web.tv": 1,
+ "on.ca": 1,
+ "onagawa.miyagi.jp": 1,
+ "onga.fukuoka.jp": 1,
+ "onjuku.chiba.jp": 1,
+ "online.museum": 1,
+ "onna.okinawa.jp": 1,
+ "ono.fukui.jp": 1,
+ "ono.fukushima.jp": 1,
+ "ono.hyogo.jp": 1,
+ "onojo.fukuoka.jp": 1,
+ "onomichi.hiroshima.jp": 1,
+ "ontario.museum": 1,
+ "ookuwa.nagano.jp": 1,
+ "ooshika.nagano.jp": 1,
+ "openair.museum": 1,
+ "operaunite.com": 1,
+ "opoczno.pl": 1,
+ "opole.pl": 1,
+ "oppdal.no": 1,
+ "oppegard.no": 1,
+ "oppeg\u00e5rd.no": 1,
+ "or.at": 1,
+ "or.bi": 1,
+ "or.ci": 1,
+ "or.cr": 1,
+ "or.id": 1,
+ "or.it": 1,
+ "or.jp": 1,
+ "or.kr": 1,
+ "or.mu": 1,
+ "or.na": 1,
+ "or.pw": 1,
+ "or.th": 1,
+ "or.tz": 1,
+ "or.ug": 1,
+ "or.us": 1,
+ "ora.gunma.jp": 1,
+ "oregon.museum": 1,
+ "oregontrail.museum": 1,
+ "orenburg.ru": 1,
+ "org.ac": 1,
+ "org.ae": 1,
+ "org.af": 1,
+ "org.ag": 1,
+ "org.ai": 1,
+ "org.al": 1,
+ "org.an": 1,
+ "org.au": 1,
+ "org.az": 1,
+ "org.ba": 1,
+ "org.bb": 1,
+ "org.bh": 1,
+ "org.bi": 1,
+ "org.bm": 1,
+ "org.bo": 1,
+ "org.br": 1,
+ "org.bs": 1,
+ "org.bt": 1,
+ "org.bw": 1,
+ "org.bz": 1,
+ "org.ci": 1,
+ "org.cn": 1,
+ "org.co": 1,
+ "org.cu": 1,
+ "org.dm": 1,
+ "org.do": 1,
+ "org.dz": 1,
+ "org.ec": 1,
+ "org.ee": 1,
+ "org.eg": 1,
+ "org.es": 1,
+ "org.ge": 1,
+ "org.gg": 1,
+ "org.gh": 1,
+ "org.gi": 1,
+ "org.gn": 1,
+ "org.gp": 1,
+ "org.gr": 1,
+ "org.hk": 1,
+ "org.hn": 1,
+ "org.ht": 1,
+ "org.hu": 1,
+ "org.im": 1,
+ "org.in": 1,
+ "org.iq": 1,
+ "org.ir": 1,
+ "org.is": 1,
+ "org.je": 1,
+ "org.jo": 1,
+ "org.kg": 1,
+ "org.ki": 1,
+ "org.km": 1,
+ "org.kn": 1,
+ "org.kp": 1,
+ "org.ky": 1,
+ "org.kz": 1,
+ "org.la": 1,
+ "org.lb": 1,
+ "org.lc": 1,
+ "org.lk": 1,
+ "org.lr": 1,
+ "org.ls": 1,
+ "org.lv": 1,
+ "org.ly": 1,
+ "org.ma": 1,
+ "org.me": 1,
+ "org.mg": 1,
+ "org.mk": 1,
+ "org.ml": 1,
+ "org.mn": 1,
+ "org.mo": 1,
+ "org.mu": 1,
+ "org.mv": 1,
+ "org.mw": 1,
+ "org.mx": 1,
+ "org.my": 1,
+ "org.na": 1,
+ "org.ng": 1,
+ "org.nr": 1,
+ "org.pa": 1,
+ "org.pe": 1,
+ "org.pf": 1,
+ "org.ph": 1,
+ "org.pk": 1,
+ "org.pl": 1,
+ "org.pn": 1,
+ "org.pr": 1,
+ "org.ps": 1,
+ "org.pt": 1,
+ "org.qa": 1,
+ "org.ro": 1,
+ "org.rs": 1,
+ "org.ru": 1,
+ "org.sa": 1,
+ "org.sb": 1,
+ "org.sc": 1,
+ "org.sd": 1,
+ "org.se": 1,
+ "org.sg": 1,
+ "org.sh": 1,
+ "org.sl": 1,
+ "org.sn": 1,
+ "org.so": 1,
+ "org.st": 1,
+ "org.sy": 1,
+ "org.sz": 1,
+ "org.tj": 1,
+ "org.tm": 1,
+ "org.tn": 1,
+ "org.to": 1,
+ "org.tt": 1,
+ "org.tw": 1,
+ "org.ua": 1,
+ "org.ug": 1,
+ "org.uy": 1,
+ "org.uz": 1,
+ "org.vc": 1,
+ "org.ve": 1,
+ "org.vi": 1,
+ "org.vn": 1,
+ "org.ws": 1,
+ "oristano.it": 1,
+ "orkanger.no": 1,
+ "orkdal.no": 1,
+ "orland.no": 1,
+ "orskog.no": 1,
+ "orsta.no": 1,
+ "oryol.ru": 1,
+ "os.hedmark.no": 1,
+ "os.hordaland.no": 1,
+ "osaka.jp": 1,
+ "osakasayama.osaka.jp": 1,
+ "osaki.miyagi.jp": 1,
+ "osakikamijima.hiroshima.jp": 1,
+ "osen.no": 1,
+ "oseto.nagasaki.jp": 1,
+ "oshima.tokyo.jp": 1,
+ "oshima.yamaguchi.jp": 1,
+ "oshino.yamanashi.jp": 1,
+ "oshu.iwate.jp": 1,
+ "oskol.ru": 1,
+ "oslo.no": 1,
+ "osoyro.no": 1,
+ "osteroy.no": 1,
+ "oster\u00f8y.no": 1,
+ "ostre-toten.no": 1,
+ "ostroda.pl": 1,
+ "ostroleka.pl": 1,
+ "ostrowiec.pl": 1,
+ "ostrowwlkp.pl": 1,
+ "os\u00f8yro.no": 1,
+ "ot.it": 1,
+ "ota.gunma.jp": 1,
+ "ota.tokyo.jp": 1,
+ "otago.museum": 1,
+ "otake.hiroshima.jp": 1,
+ "otaki.chiba.jp": 1,
+ "otaki.nagano.jp": 1,
+ "otaki.saitama.jp": 1,
+ "otama.fukushima.jp": 1,
+ "otari.nagano.jp": 1,
+ "otaru.hokkaido.jp": 1,
+ "other.nf": 1,
+ "oto.fukuoka.jp": 1,
+ "otobe.hokkaido.jp": 1,
+ "otofuke.hokkaido.jp": 1,
+ "otoineppu.hokkaido.jp": 1,
+ "otoyo.kochi.jp": 1,
+ "otsu.shiga.jp": 1,
+ "otsuchi.iwate.jp": 1,
+ "otsuki.kochi.jp": 1,
+ "otsuki.yamanashi.jp": 1,
+ "ouchi.saga.jp": 1,
+ "ouda.nara.jp": 1,
+ "oumu.hokkaido.jp": 1,
+ "overhalla.no": 1,
+ "ovre-eiker.no": 1,
+ "owani.aomori.jp": 1,
+ "owariasahi.aichi.jp": 1,
+ "oxford.museum": 1,
+ "oyabe.toyama.jp": 1,
+ "oyama.tochigi.jp": 1,
+ "oyamazaki.kyoto.jp": 1,
+ "oyer.no": 1,
+ "oygarden.no": 1,
+ "oyodo.nara.jp": 1,
+ "oystre-slidre.no": 1,
+ "oz.au": 1,
+ "ozora.hokkaido.jp": 1,
+ "ozu.ehime.jp": 1,
+ "ozu.kumamoto.jp": 1,
+ "p.bg": 1,
+ "p.se": 1,
+ "pa.gov.pl": 1,
+ "pa.it": 1,
+ "pa.us": 1,
+ "pacific.museum": 1,
+ "paderborn.museum": 1,
+ "padova.it": 1,
+ "padua.it": 1,
+ "palace.museum": 1,
+ "palana.ru": 1,
+ "paleo.museum": 1,
+ "palermo.it": 1,
+ "palmsprings.museum": 1,
+ "panama.museum": 1,
+ "parachuting.aero": 1,
+ "paragliding.aero": 1,
+ "paris.museum": 1,
+ "parliament.uk": 0,
+ "parma.it": 1,
+ "paroch.k12.ma.us": 1,
+ "parti.se": 1,
+ "pasadena.museum": 1,
+ "passenger-association.aero": 1,
+ "pavia.it": 1,
+ "pb.ao": 1,
+ "pc.it": 1,
+ "pc.pl": 1,
+ "pd.it": 1,
+ "pe.ca": 1,
+ "pe.it": 1,
+ "pe.kr": 1,
+ "penza.ru": 1,
+ "per.la": 1,
+ "per.nf": 1,
+ "per.sg": 1,
+ "perm.ru": 1,
+ "perso.ht": 1,
+ "perso.sn": 1,
+ "perso.tn": 1,
+ "perugia.it": 1,
+ "pesaro-urbino.it": 1,
+ "pesarourbino.it": 1,
+ "pescara.it": 1,
+ "pg": 2,
+ "pg.it": 1,
+ "pharmacien.fr": 1,
+ "pharmaciens.km": 1,
+ "pharmacy.museum": 1,
+ "philadelphia.museum": 1,
+ "philadelphiaarea.museum": 1,
+ "philately.museum": 1,
+ "phoenix.museum": 1,
+ "photography.museum": 1,
+ "pi.it": 1,
+ "piacenza.it": 1,
+ "pila.pl": 1,
+ "pilot.aero": 1,
+ "pilots.museum": 1,
+ "pippu.hokkaido.jp": 1,
+ "pisa.it": 1,
+ "pistoia.it": 1,
+ "pisz.pl": 1,
+ "pittsburgh.museum": 1,
+ "pl.ua": 1,
+ "planetarium.museum": 1,
+ "plantation.museum": 1,
+ "plants.museum": 1,
+ "plaza.museum": 1,
+ "plc.co.im": 1,
+ "plc.ly": 1,
+ "plo.ps": 1,
+ "pn.it": 1,
+ "po.gov.pl": 1,
+ "po.it": 1,
+ "podhale.pl": 1,
+ "podlasie.pl": 1,
+ "podzone.net": 1,
+ "podzone.org": 1,
+ "pol.dz": 1,
+ "pol.ht": 1,
+ "police.uk": 2,
+ "polkowice.pl": 1,
+ "poltava.ua": 1,
+ "pomorskie.pl": 1,
+ "pomorze.pl": 1,
+ "pordenone.it": 1,
+ "porsanger.no": 1,
+ "porsangu.no": 1,
+ "porsgrunn.no": 1,
+ "pors\u00e1\u014bgu.no": 1,
+ "port.fr": 1,
+ "portal.museum": 1,
+ "portland.museum": 1,
+ "portlligat.museum": 1,
+ "posts-and-telecommunications.museum": 1,
+ "potenza.it": 1,
+ "powiat.pl": 1,
+ "poznan.pl": 1,
+ "pp.az": 1,
+ "pp.ru": 1,
+ "pp.se": 1,
+ "pp.ua": 1,
+ "ppg.br": 1,
+ "pr.it": 1,
+ "pr.us": 1,
+ "prato.it": 1,
+ "prd.fr": 1,
+ "prd.km": 1,
+ "prd.mg": 1,
+ "preservation.museum": 1,
+ "presidio.museum": 1,
+ "press.aero": 1,
+ "press.ma": 1,
+ "press.museum": 1,
+ "press.se": 1,
+ "presse.ci": 1,
+ "presse.fr": 1,
+ "presse.km": 1,
+ "presse.ml": 1,
+ "pri.ee": 1,
+ "principe.st": 1,
+ "priv.at": 1,
+ "priv.hu": 1,
+ "priv.me": 1,
+ "priv.no": 1,
+ "priv.pl": 1,
+ "pro.az": 1,
+ "pro.br": 1,
+ "pro.ec": 1,
+ "pro.ht": 1,
+ "pro.mv": 1,
+ "pro.na": 1,
+ "pro.pr": 1,
+ "pro.tt": 1,
+ "pro.vn": 1,
+ "prochowice.pl": 1,
+ "production.aero": 1,
+ "prof.pr": 1,
+ "project.museum": 1,
+ "promocion.ar": 0,
+ "pruszkow.pl": 1,
+ "przeworsk.pl": 1,
+ "psc.br": 1,
+ "psi.br": 1,
+ "pskov.ru": 1,
+ "pt.it": 1,
+ "ptz.ru": 1,
+ "pu.it": 1,
+ "pub.sa": 1,
+ "publ.pt": 1,
+ "public.museum": 1,
+ "pubol.museum": 1,
+ "pulawy.pl": 1,
+ "pv.it": 1,
+ "pvt.ge": 1,
+ "pvt.k12.ma.us": 1,
+ "py": 2,
+ "pyatigorsk.ru": 1,
+ "pz.it": 1,
+ "q.bg": 1,
+ "qc.ca": 1,
+ "qc.com": 1,
+ "qh.cn": 1,
+ "qld.au": 1,
+ "qld.edu.au": 1,
+ "qld.gov.au": 1,
+ "qsl.br": 1,
+ "quebec.museum": 1,
+ "r.bg": 1,
+ "r.se": 1,
+ "ra.it": 1,
+ "rade.no": 1,
+ "radio.br": 1,
+ "radom.pl": 1,
+ "radoy.no": 1,
+ "rad\u00f8y.no": 1,
+ "ragusa.it": 1,
+ "rahkkeravju.no": 1,
+ "raholt.no": 1,
+ "railroad.museum": 1,
+ "railway.museum": 1,
+ "raisa.no": 1,
+ "rakkestad.no": 1,
+ "rakpetroleum.om": 0,
+ "ralingen.no": 1,
+ "rana.no": 1,
+ "randaberg.no": 1,
+ "rankoshi.hokkaido.jp": 1,
+ "ranzan.saitama.jp": 1,
+ "rauma.no": 1,
+ "ravenna.it": 1,
+ "rawa-maz.pl": 1,
+ "rc.it": 1,
+ "re.it": 1,
+ "re.kr": 1,
+ "readmyblog.org": 1,
+ "realestate.pl": 1,
+ "rebun.hokkaido.jp": 1,
+ "rec.br": 1,
+ "rec.co": 1,
+ "rec.nf": 1,
+ "rec.ro": 1,
+ "recreation.aero": 1,
+ "reggio-calabria.it": 1,
+ "reggio-emilia.it": 1,
+ "reggiocalabria.it": 1,
+ "reggioemilia.it": 1,
+ "reklam.hu": 1,
+ "rel.ht": 1,
+ "rel.pl": 1,
+ "rendalen.no": 1,
+ "rennebu.no": 1,
+ "rennesoy.no": 1,
+ "rennes\u00f8y.no": 1,
+ "rep.kp": 1,
+ "repbody.aero": 1,
+ "res.aero": 1,
+ "res.in": 1,
+ "research.aero": 1,
+ "research.museum": 1,
+ "resistance.museum": 1,
+ "retina.ar": 0,
+ "rg.it": 1,
+ "ri.it": 1,
+ "ri.us": 1,
+ "rieti.it": 1,
+ "rifu.miyagi.jp": 1,
+ "riik.ee": 1,
+ "rikubetsu.hokkaido.jp": 1,
+ "rikuzentakata.iwate.jp": 1,
+ "rimini.it": 1,
+ "rindal.no": 1,
+ "ringebu.no": 1,
+ "ringerike.no": 1,
+ "ringsaker.no": 1,
+ "riodejaneiro.museum": 1,
+ "rishiri.hokkaido.jp": 1,
+ "rishirifuji.hokkaido.jp": 1,
+ "risor.no": 1,
+ "rissa.no": 1,
+ "ris\u00f8r.no": 1,
+ "ritto.shiga.jp": 1,
+ "rivne.ua": 1,
+ "rl.no": 1,
+ "rm.it": 1,
+ "rn.it": 1,
+ "rnd.ru": 1,
+ "rnrt.tn": 1,
+ "rns.tn": 1,
+ "rnu.tn": 1,
+ "ro.it": 1,
+ "roan.no": 1,
+ "rochester.museum": 1,
+ "rockart.museum": 1,
+ "rodoy.no": 1,
+ "rokunohe.aomori.jp": 1,
+ "rollag.no": 1,
+ "roma.it": 1,
+ "roma.museum": 1,
+ "rome.it": 1,
+ "romsa.no": 1,
+ "romskog.no": 1,
+ "roros.no": 1,
+ "rost.no": 1,
+ "rotorcraft.aero": 1,
+ "rovigo.it": 1,
+ "rovno.ua": 1,
+ "royken.no": 1,
+ "royrvik.no": 1,
+ "rs.ba": 1,
+ "ru.com": 1,
+ "rubtsovsk.ru": 1,
+ "ruovat.no": 1,
+ "russia.museum": 1,
+ "rv.ua": 1,
+ "ryazan.ru": 1,
+ "rybnik.pl": 1,
+ "rygge.no": 1,
+ "ryokami.saitama.jp": 1,
+ "ryugasaki.ibaraki.jp": 1,
+ "ryuoh.shiga.jp": 1,
+ "rzeszow.pl": 1,
+ "r\u00e1hkker\u00e1vju.no": 1,
+ "r\u00e1isa.no": 1,
+ "r\u00e5de.no": 1,
+ "r\u00e5holt.no": 1,
+ "r\u00e6lingen.no": 1,
+ "r\u00f8d\u00f8y.no": 1,
+ "r\u00f8mskog.no": 1,
+ "r\u00f8ros.no": 1,
+ "r\u00f8st.no": 1,
+ "r\u00f8yken.no": 1,
+ "r\u00f8yrvik.no": 1,
+ "s.bg": 1,
+ "s.se": 1,
+ "sa.au": 1,
+ "sa.com": 1,
+ "sa.cr": 1,
+ "sa.edu.au": 1,
+ "sa.gov.au": 1,
+ "sa.it": 1,
+ "sabae.fukui.jp": 1,
+ "sado.niigata.jp": 1,
+ "safety.aero": 1,
+ "saga.jp": 1,
+ "saga.saga.jp": 1,
+ "sagae.yamagata.jp": 1,
+ "sagamihara.kanagawa.jp": 1,
+ "saigawa.fukuoka.jp": 1,
+ "saijo.ehime.jp": 1,
+ "saikai.nagasaki.jp": 1,
+ "saiki.oita.jp": 1,
+ "saintlouis.museum": 1,
+ "saitama.jp": 1,
+ "saitama.saitama.jp": 1,
+ "saito.miyazaki.jp": 1,
+ "saka.hiroshima.jp": 1,
+ "sakado.saitama.jp": 1,
+ "sakae.chiba.jp": 1,
+ "sakae.nagano.jp": 1,
+ "sakahogi.gifu.jp": 1,
+ "sakai.fukui.jp": 1,
+ "sakai.ibaraki.jp": 1,
+ "sakai.osaka.jp": 1,
+ "sakaiminato.tottori.jp": 1,
+ "sakaki.nagano.jp": 1,
+ "sakata.yamagata.jp": 1,
+ "sakawa.kochi.jp": 1,
+ "sakegawa.yamagata.jp": 1,
+ "sakhalin.ru": 1,
+ "saku.nagano.jp": 1,
+ "sakuho.nagano.jp": 1,
+ "sakura.chiba.jp": 1,
+ "sakura.tochigi.jp": 1,
+ "sakuragawa.ibaraki.jp": 1,
+ "sakurai.nara.jp": 1,
+ "sakyo.kyoto.jp": 1,
+ "salangen.no": 1,
+ "salat.no": 1,
+ "salem.museum": 1,
+ "salerno.it": 1,
+ "saltdal.no": 1,
+ "salvadordali.museum": 1,
+ "salzburg.museum": 1,
+ "samara.ru": 1,
+ "samegawa.fukushima.jp": 1,
+ "samnanger.no": 1,
+ "samukawa.kanagawa.jp": 1,
+ "sanagochi.tokushima.jp": 1,
+ "sanda.hyogo.jp": 1,
+ "sande.more-og-romsdal.no": 1,
+ "sande.m\u00f8re-og-romsdal.no": 1,
+ "sande.vestfold.no": 1,
+ "sandefjord.no": 1,
+ "sandiego.museum": 1,
+ "sandnes.no": 1,
+ "sandnessjoen.no": 1,
+ "sandnessj\u00f8en.no": 1,
+ "sandoy.no": 1,
+ "sand\u00f8y.no": 1,
+ "sanfrancisco.museum": 1,
+ "sango.nara.jp": 1,
+ "sanjo.niigata.jp": 1,
+ "sannan.hyogo.jp": 1,
+ "sannohe.aomori.jp": 1,
+ "sano.tochigi.jp": 1,
+ "sanok.pl": 1,
+ "santabarbara.museum": 1,
+ "santacruz.museum": 1,
+ "santafe.museum": 1,
+ "sanuki.kagawa.jp": 1,
+ "saotome.st": 1,
+ "sapporo.jp": 2,
+ "saratov.ru": 1,
+ "saroma.hokkaido.jp": 1,
+ "sarpsborg.no": 1,
+ "sarufutsu.hokkaido.jp": 1,
+ "sasaguri.fukuoka.jp": 1,
+ "sasayama.hyogo.jp": 1,
+ "sasebo.nagasaki.jp": 1,
+ "saskatchewan.museum": 1,
+ "sassari.it": 1,
+ "satosho.okayama.jp": 1,
+ "satsumasendai.kagoshima.jp": 1,
+ "satte.saitama.jp": 1,
+ "satx.museum": 1,
+ "sauda.no": 1,
+ "sauherad.no": 1,
+ "savannahga.museum": 1,
+ "saves-the-whales.com": 1,
+ "savona.it": 1,
+ "sayama.osaka.jp": 1,
+ "sayama.saitama.jp": 1,
+ "sayo.hyogo.jp": 1,
+ "sb.ua": 1,
+ "sc.cn": 1,
+ "sc.kr": 1,
+ "sc.tz": 1,
+ "sc.ug": 1,
+ "sc.us": 1,
+ "sch.ae": 1,
+ "sch.gg": 1,
+ "sch.id": 1,
+ "sch.ir": 1,
+ "sch.je": 1,
+ "sch.jo": 1,
+ "sch.lk": 1,
+ "sch.ly": 1,
+ "sch.qa": 1,
+ "sch.sa": 1,
+ "sch.uk": 2,
+ "schlesisches.museum": 1,
+ "schoenbrunn.museum": 1,
+ "schokoladen.museum": 1,
+ "school.museum": 1,
+ "school.na": 1,
+ "schweiz.museum": 1,
+ "sci.eg": 1,
+ "science-fiction.museum": 1,
+ "science.museum": 1,
+ "scienceandhistory.museum": 1,
+ "scienceandindustry.museum": 1,
+ "sciencecenter.museum": 1,
+ "sciencecenters.museum": 1,
+ "sciencehistory.museum": 1,
+ "sciences.museum": 1,
+ "sciencesnaturelles.museum": 1,
+ "scientist.aero": 1,
+ "scotland.museum": 1,
+ "scrapper-site.net": 1,
+ "scrapping.cc": 1,
+ "sd.cn": 1,
+ "sd.us": 1,
+ "se.com": 1,
+ "se.net": 1,
+ "seaport.museum": 1,
+ "sebastopol.ua": 1,
+ "sec.ps": 1,
+ "seihi.nagasaki.jp": 1,
+ "seika.kyoto.jp": 1,
+ "seiro.niigata.jp": 1,
+ "seirou.niigata.jp": 1,
+ "seiyo.ehime.jp": 1,
+ "sejny.pl": 1,
+ "seki.gifu.jp": 1,
+ "sekigahara.gifu.jp": 1,
+ "sekikawa.niigata.jp": 1,
+ "sel.no": 1,
+ "selbu.no": 1,
+ "selfip.biz": 1,
+ "selfip.com": 1,
+ "selfip.info": 1,
+ "selfip.net": 1,
+ "selfip.org": 1,
+ "selje.no": 1,
+ "seljord.no": 1,
+ "sells-for-less.com": 1,
+ "sells-for-u.com": 1,
+ "sells-it.net": 1,
+ "sellsyourhome.org": 1,
+ "semboku.akita.jp": 1,
+ "semine.miyagi.jp": 1,
+ "sendai.jp": 2,
+ "sennan.osaka.jp": 1,
+ "seoul.kr": 1,
+ "sera.hiroshima.jp": 1,
+ "seranishi.hiroshima.jp": 1,
+ "servebbs.com": 1,
+ "servebbs.net": 1,
+ "servebbs.org": 1,
+ "serveftp.net": 1,
+ "serveftp.org": 1,
+ "servegame.org": 1,
+ "services.aero": 1,
+ "setagaya.tokyo.jp": 1,
+ "seto.aichi.jp": 1,
+ "setouchi.okayama.jp": 1,
+ "settlement.museum": 1,
+ "settlers.museum": 1,
+ "settsu.osaka.jp": 1,
+ "sevastopol.ua": 1,
+ "sex.hu": 1,
+ "sex.pl": 1,
+ "sf.no": 1,
+ "sh.cn": 1,
+ "shacknet.nu": 1,
+ "shakotan.hokkaido.jp": 1,
+ "shari.hokkaido.jp": 1,
+ "shell.museum": 1,
+ "sherbrooke.museum": 1,
+ "shibata.miyagi.jp": 1,
+ "shibata.niigata.jp": 1,
+ "shibecha.hokkaido.jp": 1,
+ "shibetsu.hokkaido.jp": 1,
+ "shibukawa.gunma.jp": 1,
+ "shibuya.tokyo.jp": 1,
+ "shichikashuku.miyagi.jp": 1,
+ "shichinohe.aomori.jp": 1,
+ "shiga.jp": 1,
+ "shiiba.miyazaki.jp": 1,
+ "shijonawate.osaka.jp": 1,
+ "shika.ishikawa.jp": 1,
+ "shikabe.hokkaido.jp": 1,
+ "shikama.miyagi.jp": 1,
+ "shikaoi.hokkaido.jp": 1,
+ "shikatsu.aichi.jp": 1,
+ "shiki.saitama.jp": 1,
+ "shikokuchuo.ehime.jp": 1,
+ "shima.mie.jp": 1,
+ "shimabara.nagasaki.jp": 1,
+ "shimada.shizuoka.jp": 1,
+ "shimamaki.hokkaido.jp": 1,
+ "shimamoto.osaka.jp": 1,
+ "shimane.jp": 1,
+ "shimane.shimane.jp": 1,
+ "shimizu.hokkaido.jp": 1,
+ "shimizu.shizuoka.jp": 1,
+ "shimoda.shizuoka.jp": 1,
+ "shimodate.ibaraki.jp": 1,
+ "shimofusa.chiba.jp": 1,
+ "shimogo.fukushima.jp": 1,
+ "shimoichi.nara.jp": 1,
+ "shimoji.okinawa.jp": 1,
+ "shimokawa.hokkaido.jp": 1,
+ "shimokitayama.nara.jp": 1,
+ "shimonita.gunma.jp": 1,
+ "shimonoseki.yamaguchi.jp": 1,
+ "shimosuwa.nagano.jp": 1,
+ "shimotsuke.tochigi.jp": 1,
+ "shimotsuma.ibaraki.jp": 1,
+ "shinagawa.tokyo.jp": 1,
+ "shinanomachi.nagano.jp": 1,
+ "shingo.aomori.jp": 1,
+ "shingu.fukuoka.jp": 1,
+ "shingu.hyogo.jp": 1,
+ "shingu.wakayama.jp": 1,
+ "shinichi.hiroshima.jp": 1,
+ "shinjo.nara.jp": 1,
+ "shinjo.okayama.jp": 1,
+ "shinjo.yamagata.jp": 1,
+ "shinjuku.tokyo.jp": 1,
+ "shinkamigoto.nagasaki.jp": 1,
+ "shinonsen.hyogo.jp": 1,
+ "shinshinotsu.hokkaido.jp": 1,
+ "shinshiro.aichi.jp": 1,
+ "shinto.gunma.jp": 1,
+ "shintoku.hokkaido.jp": 1,
+ "shintomi.miyazaki.jp": 1,
+ "shinyoshitomi.fukuoka.jp": 1,
+ "shiogama.miyagi.jp": 1,
+ "shiojiri.nagano.jp": 1,
+ "shioya.tochigi.jp": 1,
+ "shirahama.wakayama.jp": 1,
+ "shirakawa.fukushima.jp": 1,
+ "shirakawa.gifu.jp": 1,
+ "shirako.chiba.jp": 1,
+ "shiranuka.hokkaido.jp": 1,
+ "shiraoi.hokkaido.jp": 1,
+ "shiraoka.saitama.jp": 1,
+ "shirataka.yamagata.jp": 1,
+ "shiriuchi.hokkaido.jp": 1,
+ "shiroi.chiba.jp": 1,
+ "shiroishi.miyagi.jp": 1,
+ "shiroishi.saga.jp": 1,
+ "shirosato.ibaraki.jp": 1,
+ "shishikui.tokushima.jp": 1,
+ "shiso.hyogo.jp": 1,
+ "shisui.chiba.jp": 1,
+ "shitara.aichi.jp": 1,
+ "shiwa.iwate.jp": 1,
+ "shizukuishi.iwate.jp": 1,
+ "shizuoka.jp": 1,
+ "shizuoka.shizuoka.jp": 1,
+ "shobara.hiroshima.jp": 1,
+ "shonai.fukuoka.jp": 1,
+ "shonai.yamagata.jp": 1,
+ "shoo.okayama.jp": 1,
+ "shop.ht": 1,
+ "shop.hu": 1,
+ "shop.pl": 1,
+ "show.aero": 1,
+ "showa.fukushima.jp": 1,
+ "showa.gunma.jp": 1,
+ "showa.yamanashi.jp": 1,
+ "shunan.yamaguchi.jp": 1,
+ "si.it": 1,
+ "sibenik.museum": 1,
+ "siedlce.pl": 1,
+ "siellak.no": 1,
+ "siemens.om": 0,
+ "siena.it": 1,
+ "sigdal.no": 1,
+ "siljan.no": 1,
+ "silk.museum": 1,
+ "simbirsk.ru": 1,
+ "simple-url.com": 1,
+ "siracusa.it": 1,
+ "sirdal.no": 1,
+ "sk.ca": 1,
+ "skanit.no": 1,
+ "skanland.no": 1,
+ "skaun.no": 1,
+ "skedsmo.no": 1,
+ "skedsmokorset.no": 1,
+ "ski.museum": 1,
+ "ski.no": 1,
+ "skien.no": 1,
+ "skierva.no": 1,
+ "skierv\u00e1.no": 1,
+ "skiptvet.no": 1,
+ "skjak.no": 1,
+ "skjervoy.no": 1,
+ "skjerv\u00f8y.no": 1,
+ "skj\u00e5k.no": 1,
+ "sklep.pl": 1,
+ "skoczow.pl": 1,
+ "skodje.no": 1,
+ "skole.museum": 1,
+ "skydiving.aero": 1,
+ "sk\u00e1nit.no": 1,
+ "sk\u00e5nland.no": 1,
+ "slask.pl": 1,
+ "slattum.no": 1,
+ "sld.do": 1,
+ "sld.pa": 1,
+ "slg.br": 1,
+ "slupsk.pl": 1,
+ "sm.ua": 1,
+ "smola.no": 1,
+ "smolensk.ru": 1,
+ "sm\u00f8la.no": 1,
+ "sn.cn": 1,
+ "snaase.no": 1,
+ "snasa.no": 1,
+ "snillfjord.no": 1,
+ "snoasa.no": 1,
+ "snz.ru": 1,
+ "sn\u00e5ase.no": 1,
+ "sn\u00e5sa.no": 1,
+ "so.gov.pl": 1,
+ "so.it": 1,
+ "sobetsu.hokkaido.jp": 1,
+ "soc.lk": 1,
+ "society.museum": 1,
+ "sodegaura.chiba.jp": 1,
+ "soeda.fukuoka.jp": 1,
+ "software.aero": 1,
+ "sogndal.no": 1,
+ "sogne.no": 1,
+ "soja.okayama.jp": 1,
+ "soka.saitama.jp": 1,
+ "sokndal.no": 1,
+ "sola.no": 1,
+ "sologne.museum": 1,
+ "solund.no": 1,
+ "soma.fukushima.jp": 1,
+ "somna.no": 1,
+ "sondre-land.no": 1,
+ "sondrio.it": 1,
+ "songdalen.no": 1,
+ "songfest.om": 0,
+ "soni.nara.jp": 1,
+ "soo.kagoshima.jp": 1,
+ "sopot.pl": 1,
+ "sor-aurdal.no": 1,
+ "sor-fron.no": 1,
+ "sor-odal.no": 1,
+ "sor-varanger.no": 1,
+ "sorfold.no": 1,
+ "sorreisa.no": 1,
+ "sortland.no": 1,
+ "sorum.no": 1,
+ "sos.pl": 1,
+ "sosa.chiba.jp": 1,
+ "sosnowiec.pl": 1,
+ "soundandvision.museum": 1,
+ "southcarolina.museum": 1,
+ "southwest.museum": 1,
+ "sowa.ibaraki.jp": 1,
+ "sp.it": 1,
+ "space-to-rent.com": 1,
+ "space.museum": 1,
+ "spb.ru": 1,
+ "spjelkavik.no": 1,
+ "sport.hu": 1,
+ "spy.museum": 1,
+ "spydeberg.no": 1,
+ "square.museum": 1,
+ "sr.gov.pl": 1,
+ "sr.it": 1,
+ "srv.br": 1,
+ "ss.it": 1,
+ "sshn.se": 1,
+ "st.no": 1,
+ "stadt.museum": 1,
+ "stalbans.museum": 1,
+ "stalowa-wola.pl": 1,
+ "stange.no": 1,
+ "starachowice.pl": 1,
+ "stargard.pl": 1,
+ "starnberg.museum": 1,
+ "starostwo.gov.pl": 1,
+ "stat.no": 1,
+ "state.museum": 1,
+ "statecouncil.om": 0,
+ "stateofdelaware.museum": 1,
+ "stathelle.no": 1,
+ "station.museum": 1,
+ "stavanger.no": 1,
+ "stavern.no": 1,
+ "stavropol.ru": 1,
+ "steam.museum": 1,
+ "steiermark.museum": 1,
+ "steigen.no": 1,
+ "steinkjer.no": 1,
+ "stjohn.museum": 1,
+ "stjordal.no": 1,
+ "stjordalshalsen.no": 1,
+ "stj\u00f8rdal.no": 1,
+ "stj\u00f8rdalshalsen.no": 1,
+ "stockholm.museum": 1,
+ "stokke.no": 1,
+ "stor-elvdal.no": 1,
+ "stord.no": 1,
+ "stordal.no": 1,
+ "store.bb": 1,
+ "store.nf": 1,
+ "store.ro": 1,
+ "store.st": 1,
+ "storfjord.no": 1,
+ "stpetersburg.museum": 1,
+ "strand.no": 1,
+ "stranda.no": 1,
+ "stryn.no": 1,
+ "student.aero": 1,
+ "stuff-4-sale.org": 1,
+ "stuff-4-sale.us": 1,
+ "stuttgart.museum": 1,
+ "stv.ru": 1,
+ "sue.fukuoka.jp": 1,
+ "suedtirol.it": 1,
+ "suginami.tokyo.jp": 1,
+ "sugito.saitama.jp": 1,
+ "suifu.ibaraki.jp": 1,
+ "suisse.museum": 1,
+ "suita.osaka.jp": 1,
+ "sukagawa.fukushima.jp": 1,
+ "sukumo.kochi.jp": 1,
+ "sula.no": 1,
+ "suldal.no": 1,
+ "suli.hu": 1,
+ "sumida.tokyo.jp": 1,
+ "sumita.iwate.jp": 1,
+ "sumoto.hyogo.jp": 1,
+ "sumoto.kumamoto.jp": 1,
+ "sumy.ua": 1,
+ "sunagawa.hokkaido.jp": 1,
+ "sund.no": 1,
+ "sunndal.no": 1,
+ "surgeonshall.museum": 1,
+ "surgut.ru": 1,
+ "surnadal.no": 1,
+ "surrey.museum": 1,
+ "susaki.kochi.jp": 1,
+ "susono.shizuoka.jp": 1,
+ "suwa.nagano.jp": 1,
+ "suwalki.pl": 1,
+ "suzaka.nagano.jp": 1,
+ "suzu.ishikawa.jp": 1,
+ "suzuka.mie.jp": 1,
+ "sv": 2,
+ "sv.it": 1,
+ "svalbard.no": 1,
+ "sveio.no": 1,
+ "svelvik.no": 1,
+ "svizzera.museum": 1,
+ "sweden.museum": 1,
+ "swidnica.pl": 1,
+ "swiebodzin.pl": 1,
+ "swinoujscie.pl": 1,
+ "sx.cn": 1,
+ "sydney.museum": 1,
+ "sykkylven.no": 1,
+ "syzran.ru": 1,
+ "szczecin.pl": 1,
+ "szczytno.pl": 1,
+ "szex.hu": 1,
+ "szkola.pl": 1,
+ "s\u00e1lat.no": 1,
+ "s\u00e1l\u00e1t.no": 1,
+ "s\u00f8gne.no": 1,
+ "s\u00f8mna.no": 1,
+ "s\u00f8ndre-land.no": 1,
+ "s\u00f8r-aurdal.no": 1,
+ "s\u00f8r-fron.no": 1,
+ "s\u00f8r-odal.no": 1,
+ "s\u00f8r-varanger.no": 1,
+ "s\u00f8rfold.no": 1,
+ "s\u00f8rreisa.no": 1,
+ "s\u00f8rum.no": 1,
+ "t.bg": 1,
+ "t.se": 1,
+ "ta.it": 1,
+ "tabayama.yamanashi.jp": 1,
+ "tabuse.yamaguchi.jp": 1,
+ "tachiarai.fukuoka.jp": 1,
+ "tachikawa.tokyo.jp": 1,
+ "tadaoka.osaka.jp": 1,
+ "tado.mie.jp": 1,
+ "tadotsu.kagawa.jp": 1,
+ "tagajo.miyagi.jp": 1,
+ "tagami.niigata.jp": 1,
+ "tagawa.fukuoka.jp": 1,
+ "tahara.aichi.jp": 1,
+ "taiji.wakayama.jp": 1,
+ "taiki.hokkaido.jp": 1,
+ "taiki.mie.jp": 1,
+ "tainai.niigata.jp": 1,
+ "taira.toyama.jp": 1,
+ "taishi.hyogo.jp": 1,
+ "taishi.osaka.jp": 1,
+ "taishin.fukushima.jp": 1,
+ "taito.tokyo.jp": 1,
+ "taiwa.miyagi.jp": 1,
+ "tajimi.gifu.jp": 1,
+ "tajiri.osaka.jp": 1,
+ "taka.hyogo.jp": 1,
+ "takagi.nagano.jp": 1,
+ "takahagi.ibaraki.jp": 1,
+ "takahama.aichi.jp": 1,
+ "takahama.fukui.jp": 1,
+ "takaharu.miyazaki.jp": 1,
+ "takahashi.okayama.jp": 1,
+ "takahata.yamagata.jp": 1,
+ "takaishi.osaka.jp": 1,
+ "takamatsu.kagawa.jp": 1,
+ "takamori.kumamoto.jp": 1,
+ "takamori.nagano.jp": 1,
+ "takanabe.miyazaki.jp": 1,
+ "takanezawa.tochigi.jp": 1,
+ "takaoka.toyama.jp": 1,
+ "takarazuka.hyogo.jp": 1,
+ "takasago.hyogo.jp": 1,
+ "takasaki.gunma.jp": 1,
+ "takashima.shiga.jp": 1,
+ "takasu.hokkaido.jp": 1,
+ "takata.fukuoka.jp": 1,
+ "takatori.nara.jp": 1,
+ "takatsuki.osaka.jp": 1,
+ "takatsuki.shiga.jp": 1,
+ "takayama.gifu.jp": 1,
+ "takayama.gunma.jp": 1,
+ "takayama.nagano.jp": 1,
+ "takazaki.miyazaki.jp": 1,
+ "takehara.hiroshima.jp": 1,
+ "taketa.oita.jp": 1,
+ "taketomi.okinawa.jp": 1,
+ "taki.mie.jp": 1,
+ "takikawa.hokkaido.jp": 1,
+ "takino.hyogo.jp": 1,
+ "takinoue.hokkaido.jp": 1,
+ "takizawa.iwate.jp": 1,
+ "takko.aomori.jp": 1,
+ "tako.chiba.jp": 1,
+ "taku.saga.jp": 1,
+ "tama.tokyo.jp": 1,
+ "tamakawa.fukushima.jp": 1,
+ "tamaki.mie.jp": 1,
+ "tamamura.gunma.jp": 1,
+ "tamano.okayama.jp": 1,
+ "tamatsukuri.ibaraki.jp": 1,
+ "tamayu.shimane.jp": 1,
+ "tamba.hyogo.jp": 1,
+ "tambov.ru": 1,
+ "tana.no": 1,
+ "tanabe.kyoto.jp": 1,
+ "tanabe.wakayama.jp": 1,
+ "tanagura.fukushima.jp": 1,
+ "tananger.no": 1,
+ "tank.museum": 1,
+ "tanohata.iwate.jp": 1,
+ "tara.saga.jp": 1,
+ "tarama.okinawa.jp": 1,
+ "taranto.it": 1,
+ "targi.pl": 1,
+ "tarnobrzeg.pl": 1,
+ "tarui.gifu.jp": 1,
+ "tarumizu.kagoshima.jp": 1,
+ "tas.au": 1,
+ "tas.edu.au": 1,
+ "tas.gov.au": 1,
+ "tatarstan.ru": 1,
+ "tatebayashi.gunma.jp": 1,
+ "tateshina.nagano.jp": 1,
+ "tateyama.chiba.jp": 1,
+ "tateyama.toyama.jp": 1,
+ "tatsuno.hyogo.jp": 1,
+ "tatsuno.nagano.jp": 1,
+ "tawaramoto.nara.jp": 1,
+ "taxi.aero": 1,
+ "taxi.br": 1,
+ "tcm.museum": 1,
+ "te.it": 1,
+ "te.ua": 1,
+ "teaches-yoga.com": 1,
+ "technology.museum": 1,
+ "telekommunikation.museum": 1,
+ "television.museum": 1,
+ "tempio-olbia.it": 1,
+ "tempioolbia.it": 1,
+ "tendo.yamagata.jp": 1,
+ "tenei.fukushima.jp": 1,
+ "tenkawa.nara.jp": 1,
+ "tenri.nara.jp": 1,
+ "teo.br": 1,
+ "teramo.it": 1,
+ "terni.it": 1,
+ "ternopil.ua": 1,
+ "teshikaga.hokkaido.jp": 1,
+ "test.ru": 1,
+ "test.tj": 1,
+ "texas.museum": 1,
+ "textile.museum": 1,
+ "tgory.pl": 1,
+ "theater.museum": 1,
+ "thruhere.net": 1,
+ "time.museum": 1,
+ "time.no": 1,
+ "timekeeping.museum": 1,
+ "tingvoll.no": 1,
+ "tinn.no": 1,
+ "tj.cn": 1,
+ "tjeldsund.no": 1,
+ "tjome.no": 1,
+ "tj\u00f8me.no": 1,
+ "tm.fr": 1,
+ "tm.hu": 1,
+ "tm.km": 1,
+ "tm.mc": 1,
+ "tm.mg": 1,
+ "tm.no": 1,
+ "tm.pl": 1,
+ "tm.ro": 1,
+ "tm.se": 1,
+ "tmp.br": 1,
+ "tn.it": 1,
+ "tn.us": 1,
+ "to.it": 1,
+ "toba.mie.jp": 1,
+ "tobe.ehime.jp": 1,
+ "tobetsu.hokkaido.jp": 1,
+ "tobishima.aichi.jp": 1,
+ "tochigi.jp": 1,
+ "tochigi.tochigi.jp": 1,
+ "tochio.niigata.jp": 1,
+ "toda.saitama.jp": 1,
+ "toei.aichi.jp": 1,
+ "toga.toyama.jp": 1,
+ "togakushi.nagano.jp": 1,
+ "togane.chiba.jp": 1,
+ "togitsu.nagasaki.jp": 1,
+ "togo.aichi.jp": 1,
+ "togura.nagano.jp": 1,
+ "tohma.hokkaido.jp": 1,
+ "tohnosho.chiba.jp": 1,
+ "toho.fukuoka.jp": 1,
+ "tokai.aichi.jp": 1,
+ "tokai.ibaraki.jp": 1,
+ "tokamachi.niigata.jp": 1,
+ "tokashiki.okinawa.jp": 1,
+ "toki.gifu.jp": 1,
+ "tokigawa.saitama.jp": 1,
+ "tokke.no": 1,
+ "tokoname.aichi.jp": 1,
+ "tokorozawa.saitama.jp": 1,
+ "tokushima.jp": 1,
+ "tokushima.tokushima.jp": 1,
+ "tokuyama.yamaguchi.jp": 1,
+ "tokyo.jp": 1,
+ "tolga.no": 1,
+ "tom.ru": 1,
+ "tomakomai.hokkaido.jp": 1,
+ "tomari.hokkaido.jp": 1,
+ "tome.miyagi.jp": 1,
+ "tomi.nagano.jp": 1,
+ "tomigusuku.okinawa.jp": 1,
+ "tomika.gifu.jp": 1,
+ "tomioka.gunma.jp": 1,
+ "tomisato.chiba.jp": 1,
+ "tomiya.miyagi.jp": 1,
+ "tomobe.ibaraki.jp": 1,
+ "tomsk.ru": 1,
+ "tonaki.okinawa.jp": 1,
+ "tonami.toyama.jp": 1,
+ "tondabayashi.osaka.jp": 1,
+ "tone.ibaraki.jp": 1,
+ "tono.iwate.jp": 1,
+ "tonosho.kagawa.jp": 1,
+ "tonsberg.no": 1,
+ "toon.ehime.jp": 1,
+ "topology.museum": 1,
+ "torahime.shiga.jp": 1,
+ "toride.ibaraki.jp": 1,
+ "torino.it": 1,
+ "torino.museum": 1,
+ "torsken.no": 1,
+ "tosa.kochi.jp": 1,
+ "tosashimizu.kochi.jp": 1,
+ "toshima.tokyo.jp": 1,
+ "tosu.saga.jp": 1,
+ "tottori.jp": 1,
+ "tottori.tottori.jp": 1,
+ "touch.museum": 1,
+ "tourism.pl": 1,
+ "tourism.tn": 1,
+ "towada.aomori.jp": 1,
+ "town.museum": 1,
+ "toya.hokkaido.jp": 1,
+ "toyako.hokkaido.jp": 1,
+ "toyama.jp": 1,
+ "toyama.toyama.jp": 1,
+ "toyo.kochi.jp": 1,
+ "toyoake.aichi.jp": 1,
+ "toyohashi.aichi.jp": 1,
+ "toyokawa.aichi.jp": 1,
+ "toyonaka.osaka.jp": 1,
+ "toyone.aichi.jp": 1,
+ "toyono.osaka.jp": 1,
+ "toyooka.hyogo.jp": 1,
+ "toyosato.shiga.jp": 1,
+ "toyota.aichi.jp": 1,
+ "toyota.yamaguchi.jp": 1,
+ "toyotomi.hokkaido.jp": 1,
+ "toyotsu.fukuoka.jp": 1,
+ "toyoura.hokkaido.jp": 1,
+ "tozawa.yamagata.jp": 1,
+ "tozsde.hu": 1,
+ "tp.it": 1,
+ "tr": 2,
+ "tr.it": 1,
+ "tr.no": 1,
+ "tra.kp": 1,
+ "trader.aero": 1,
+ "trading.aero": 1,
+ "traeumtgerade.de": 1,
+ "trainer.aero": 1,
+ "trana.no": 1,
+ "tranby.no": 1,
+ "trani-andria-barletta.it": 1,
+ "trani-barletta-andria.it": 1,
+ "traniandriabarletta.it": 1,
+ "tranibarlettaandria.it": 1,
+ "tranoy.no": 1,
+ "transport.museum": 1,
+ "tran\u00f8y.no": 1,
+ "trapani.it": 1,
+ "travel.pl": 1,
+ "travel.tt": 1,
+ "trd.br": 1,
+ "tree.museum": 1,
+ "trentino.it": 1,
+ "trento.it": 1,
+ "treviso.it": 1,
+ "trieste.it": 1,
+ "troandin.no": 1,
+ "trogstad.no": 1,
+ "trolley.museum": 1,
+ "tromsa.no": 1,
+ "tromso.no": 1,
+ "troms\u00f8.no": 1,
+ "trondheim.no": 1,
+ "trust.museum": 1,
+ "trustee.museum": 1,
+ "trysil.no": 1,
+ "tr\u00e6na.no": 1,
+ "tr\u00f8gstad.no": 1,
+ "ts.it": 1,
+ "tsaritsyn.ru": 1,
+ "tsk.ru": 1,
+ "tsu.mie.jp": 1,
+ "tsubame.niigata.jp": 1,
+ "tsubata.ishikawa.jp": 1,
+ "tsubetsu.hokkaido.jp": 1,
+ "tsuchiura.ibaraki.jp": 1,
+ "tsuga.tochigi.jp": 1,
+ "tsugaru.aomori.jp": 1,
+ "tsuiki.fukuoka.jp": 1,
+ "tsukigata.hokkaido.jp": 1,
+ "tsukiyono.gunma.jp": 1,
+ "tsukuba.ibaraki.jp": 1,
+ "tsukui.kanagawa.jp": 1,
+ "tsukumi.oita.jp": 1,
+ "tsumagoi.gunma.jp": 1,
+ "tsunan.niigata.jp": 1,
+ "tsuno.kochi.jp": 1,
+ "tsuno.miyazaki.jp": 1,
+ "tsuru.yamanashi.jp": 1,
+ "tsuruga.fukui.jp": 1,
+ "tsurugashima.saitama.jp": 1,
+ "tsurugi.ishikawa.jp": 1,
+ "tsuruoka.yamagata.jp": 1,
+ "tsuruta.aomori.jp": 1,
+ "tsushima.aichi.jp": 1,
+ "tsushima.nagasaki.jp": 1,
+ "tsuwano.shimane.jp": 1,
+ "tsuyama.okayama.jp": 1,
+ "tula.ru": 1,
+ "tur.br": 1,
+ "turek.pl": 1,
+ "turen.tn": 1,
+ "turin.it": 1,
+ "turystyka.pl": 1,
+ "tuva.ru": 1,
+ "tv.bo": 1,
+ "tv.br": 1,
+ "tv.it": 1,
+ "tv.na": 1,
+ "tv.sd": 1,
+ "tvedestrand.no": 1,
+ "tver.ru": 1,
+ "tw.cn": 1,
+ "tx.us": 1,
+ "tychy.pl": 1,
+ "tydal.no": 1,
+ "tynset.no": 1,
+ "tysfjord.no": 1,
+ "tysnes.no": 1,
+ "tysvar.no": 1,
+ "tysv\u00e6r.no": 1,
+ "tyumen.ru": 1,
+ "t\u00f8nsberg.no": 1,
+ "u.bg": 1,
+ "u.se": 1,
+ "uba.ar": 0,
+ "ube.yamaguchi.jp": 1,
+ "uchihara.ibaraki.jp": 1,
+ "uchiko.ehime.jp": 1,
+ "uchinada.ishikawa.jp": 1,
+ "uchinomi.kagawa.jp": 1,
+ "ud.it": 1,
+ "uda.nara.jp": 1,
+ "udine.it": 1,
+ "udm.ru": 1,
+ "udmurtia.ru": 1,
+ "udono.mie.jp": 1,
+ "ueda.nagano.jp": 1,
+ "ueno.gunma.jp": 1,
+ "uenohara.yamanashi.jp": 1,
+ "ug.gov.pl": 1,
+ "uhren.museum": 1,
+ "uji.kyoto.jp": 1,
+ "ujiie.tochigi.jp": 1,
+ "ujitawara.kyoto.jp": 1,
+ "uk": 2,
+ "uk.com": 1,
+ "uk.net": 1,
+ "uki.kumamoto.jp": 1,
+ "ukiha.fukuoka.jp": 1,
+ "ulan-ude.ru": 1,
+ "ullensaker.no": 1,
+ "ullensvang.no": 1,
+ "ulm.museum": 1,
+ "ulsan.kr": 1,
+ "ulvik.no": 1,
+ "um.gov.pl": 1,
+ "umaji.kochi.jp": 1,
+ "umi.fukuoka.jp": 1,
+ "unazuki.toyama.jp": 1,
+ "unbi.ba": 1,
+ "undersea.museum": 1,
+ "union.aero": 1,
+ "univ.sn": 1,
+ "university.museum": 1,
+ "unjarga.no": 1,
+ "unj\u00e1rga.no": 1,
+ "unnan.shimane.jp": 1,
+ "unsa.ba": 1,
+ "unzen.nagasaki.jp": 1,
+ "uonuma.niigata.jp": 1,
+ "uozu.toyama.jp": 1,
+ "upow.gov.pl": 1,
+ "urakawa.hokkaido.jp": 1,
+ "urasoe.okinawa.jp": 1,
+ "urausu.hokkaido.jp": 1,
+ "urawa.saitama.jp": 1,
+ "urayasu.chiba.jp": 1,
+ "urbino-pesaro.it": 1,
+ "urbinopesaro.it": 1,
+ "ureshino.mie.jp": 1,
+ "uri.arpa": 1,
+ "urn.arpa": 1,
+ "uruma.okinawa.jp": 1,
+ "uryu.hokkaido.jp": 1,
+ "us.com": 1,
+ "us.na": 1,
+ "us.org": 1,
+ "usa.museum": 1,
+ "usa.oita.jp": 1,
+ "usantiques.museum": 1,
+ "usarts.museum": 1,
+ "uscountryestate.museum": 1,
+ "usculture.museum": 1,
+ "usdecorativearts.museum": 1,
+ "usenet.pl": 1,
+ "usgarden.museum": 1,
+ "ushiku.ibaraki.jp": 1,
+ "ushistory.museum": 1,
+ "ushuaia.museum": 1,
+ "uslivinghistory.museum": 1,
+ "ustka.pl": 1,
+ "usui.fukuoka.jp": 1,
+ "usuki.oita.jp": 1,
+ "ut.us": 1,
+ "utah.museum": 1,
+ "utashinai.hokkaido.jp": 1,
+ "utazas.hu": 1,
+ "utazu.kagawa.jp": 1,
+ "uto.kumamoto.jp": 1,
+ "utsira.no": 1,
+ "utsunomiya.tochigi.jp": 1,
+ "uvic.museum": 1,
+ "uw.gov.pl": 1,
+ "uwajima.ehime.jp": 1,
+ "uy.com": 1,
+ "uz.ua": 1,
+ "uzhgorod.ua": 1,
+ "v.bg": 1,
+ "va.it": 1,
+ "va.no": 1,
+ "va.us": 1,
+ "vaapste.no": 1,
+ "vadso.no": 1,
+ "vads\u00f8.no": 1,
+ "vaga.no": 1,
+ "vagan.no": 1,
+ "vagsoy.no": 1,
+ "vaksdal.no": 1,
+ "valer.hedmark.no": 1,
+ "valer.ostfold.no": 1,
+ "valle.no": 1,
+ "valley.museum": 1,
+ "vang.no": 1,
+ "vantaa.museum": 1,
+ "vanylven.no": 1,
+ "vardo.no": 1,
+ "vard\u00f8.no": 1,
+ "varese.it": 1,
+ "varggat.no": 1,
+ "varoy.no": 1,
+ "vb.it": 1,
+ "vc.it": 1,
+ "vdonsk.ru": 1,
+ "ve.it": 1,
+ "vefsn.no": 1,
+ "vega.no": 1,
+ "vegarshei.no": 1,
+ "veg\u00e5rshei.no": 1,
+ "venezia.it": 1,
+ "venice.it": 1,
+ "vennesla.no": 1,
+ "verbania.it": 1,
+ "vercelli.it": 1,
+ "verdal.no": 1,
+ "verona.it": 1,
+ "verran.no": 1,
+ "versailles.museum": 1,
+ "vestby.no": 1,
+ "vestnes.no": 1,
+ "vestre-slidre.no": 1,
+ "vestre-toten.no": 1,
+ "vestvagoy.no": 1,
+ "vestv\u00e5g\u00f8y.no": 1,
+ "vet.br": 1,
+ "veterinaire.fr": 1,
+ "veterinaire.km": 1,
+ "vevelstad.no": 1,
+ "vf.no": 1,
+ "vgs.no": 1,
+ "vi.it": 1,
+ "vi.us": 1,
+ "vibo-valentia.it": 1,
+ "vibovalentia.it": 1,
+ "vic.au": 1,
+ "vic.edu.au": 1,
+ "vic.gov.au": 1,
+ "vicenza.it": 1,
+ "video.hu": 1,
+ "vik.no": 1,
+ "viking.museum": 1,
+ "vikna.no": 1,
+ "village.museum": 1,
+ "vindafjord.no": 1,
+ "vinnica.ua": 1,
+ "vinnytsia.ua": 1,
+ "virginia.museum": 1,
+ "virtual.museum": 1,
+ "virtuel.museum": 1,
+ "viterbo.it": 1,
+ "vlaanderen.museum": 1,
+ "vladikavkaz.ru": 1,
+ "vladimir.ru": 1,
+ "vladivostok.ru": 1,
+ "vlog.br": 1,
+ "vn.ua": 1,
+ "voagat.no": 1,
+ "volda.no": 1,
+ "volgograd.ru": 1,
+ "volkenkunde.museum": 1,
+ "vologda.ru": 1,
+ "volyn.ua": 1,
+ "voronezh.ru": 1,
+ "voss.no": 1,
+ "vossevangen.no": 1,
+ "vr.it": 1,
+ "vrn.ru": 1,
+ "vs.it": 1,
+ "vt.it": 1,
+ "vt.us": 1,
+ "vv.it": 1,
+ "vyatka.ru": 1,
+ "v\u00e1rgg\u00e1t.no": 1,
+ "v\u00e5gan.no": 1,
+ "v\u00e5gs\u00f8y.no": 1,
+ "v\u00e5g\u00e5.no": 1,
+ "v\u00e5ler.hedmark.no": 1,
+ "v\u00e5ler.\u00f8stfold.no": 1,
+ "v\u00e6r\u00f8y.no": 1,
+ "w.bg": 1,
+ "w.se": 1,
+ "wa.au": 1,
+ "wa.edu.au": 1,
+ "wa.gov.au": 1,
+ "wa.us": 1,
+ "wada.nagano.jp": 1,
+ "wajiki.tokushima.jp": 1,
+ "wajima.ishikawa.jp": 1,
+ "wakasa.fukui.jp": 1,
+ "wakasa.tottori.jp": 1,
+ "wakayama.jp": 1,
+ "wakayama.wakayama.jp": 1,
+ "wake.okayama.jp": 1,
+ "wakkanai.hokkaido.jp": 1,
+ "wakuya.miyagi.jp": 1,
+ "walbrzych.pl": 1,
+ "wales.museum": 1,
+ "wallonie.museum": 1,
+ "wanouchi.gifu.jp": 1,
+ "war.museum": 1,
+ "warabi.saitama.jp": 1,
+ "warmia.pl": 1,
+ "warszawa.pl": 1,
+ "washingtondc.museum": 1,
+ "wassamu.hokkaido.jp": 1,
+ "watarai.mie.jp": 1,
+ "watari.miyagi.jp": 1,
+ "watch-and-clock.museum": 1,
+ "watchandclock.museum": 1,
+ "waw.pl": 1,
+ "wazuka.kyoto.jp": 1,
+ "web.co": 1,
+ "web.do": 1,
+ "web.id": 1,
+ "web.lk": 1,
+ "web.nf": 1,
+ "web.pk": 1,
+ "web.tj": 1,
+ "web.ve": 1,
+ "webhop.biz": 1,
+ "webhop.info": 1,
+ "webhop.net": 1,
+ "webhop.org": 1,
+ "wegrow.pl": 1,
+ "western.museum": 1,
+ "westfalen.museum": 1,
+ "whaling.museum": 1,
+ "wi.us": 1,
+ "wielun.pl": 1,
+ "wiki.br": 1,
+ "wildlife.museum": 1,
+ "williamsburg.museum": 1,
+ "windmill.museum": 1,
+ "wlocl.pl": 1,
+ "wloclawek.pl": 1,
+ "wodzislaw.pl": 1,
+ "wolomin.pl": 1,
+ "workinggroup.aero": 1,
+ "works.aero": 1,
+ "workshop.museum": 1,
+ "worse-than.tv": 1,
+ "writesthisblog.com": 1,
+ "wroc.pl": 1,
+ "wroclaw.pl": 1,
+ "ws.na": 1,
+ "wv.us": 1,
+ "www.ck": 0,
+ "www.gt": 0,
+ "www.ro": 1,
+ "wy.us": 1,
+ "x.bg": 1,
+ "x.se": 1,
+ "xj.cn": 1,
+ "xz.cn": 1,
+ "y.bg": 1,
+ "y.se": 1,
+ "yabu.hyogo.jp": 1,
+ "yabuki.fukushima.jp": 1,
+ "yachimata.chiba.jp": 1,
+ "yachiyo.chiba.jp": 1,
+ "yachiyo.ibaraki.jp": 1,
+ "yaese.okinawa.jp": 1,
+ "yahaba.iwate.jp": 1,
+ "yahiko.niigata.jp": 1,
+ "yaita.tochigi.jp": 1,
+ "yaizu.shizuoka.jp": 1,
+ "yakage.okayama.jp": 1,
+ "yakumo.hokkaido.jp": 1,
+ "yakumo.shimane.jp": 1,
+ "yakutia.ru": 1,
+ "yalta.ua": 1,
+ "yamada.fukuoka.jp": 1,
+ "yamada.iwate.jp": 1,
+ "yamada.toyama.jp": 1,
+ "yamaga.kumamoto.jp": 1,
+ "yamagata.gifu.jp": 1,
+ "yamagata.ibaraki.jp": 1,
+ "yamagata.jp": 1,
+ "yamagata.nagano.jp": 1,
+ "yamagata.yamagata.jp": 1,
+ "yamaguchi.jp": 1,
+ "yamakita.kanagawa.jp": 1,
+ "yamal.ru": 1,
+ "yamamoto.miyagi.jp": 1,
+ "yamanakako.yamanashi.jp": 1,
+ "yamanashi.jp": 1,
+ "yamanashi.yamanashi.jp": 1,
+ "yamanobe.yamagata.jp": 1,
+ "yamanouchi.nagano.jp": 1,
+ "yamashina.kyoto.jp": 1,
+ "yamato.fukushima.jp": 1,
+ "yamato.kanagawa.jp": 1,
+ "yamato.kumamoto.jp": 1,
+ "yamatokoriyama.nara.jp": 1,
+ "yamatotakada.nara.jp": 1,
+ "yamatsuri.fukushima.jp": 1,
+ "yamazoe.nara.jp": 1,
+ "yame.fukuoka.jp": 1,
+ "yanagawa.fukuoka.jp": 1,
+ "yanaizu.fukushima.jp": 1,
+ "yao.osaka.jp": 1,
+ "yaotsu.gifu.jp": 1,
+ "yaroslavl.ru": 1,
+ "yasaka.nagano.jp": 1,
+ "yashio.saitama.jp": 1,
+ "yashiro.hyogo.jp": 1,
+ "yasu.shiga.jp": 1,
+ "yasuda.kochi.jp": 1,
+ "yasugi.shimane.jp": 1,
+ "yasuoka.nagano.jp": 1,
+ "yatomi.aichi.jp": 1,
+ "yatsuka.shimane.jp": 1,
+ "yatsushiro.kumamoto.jp": 1,
+ "yawara.ibaraki.jp": 1,
+ "yawata.kyoto.jp": 1,
+ "yawatahama.ehime.jp": 1,
+ "yazu.tottori.jp": 1,
+ "ye": 2,
+ "yekaterinburg.ru": 1,
+ "yk.ca": 1,
+ "yn.cn": 1,
+ "yoichi.hokkaido.jp": 1,
+ "yoita.niigata.jp": 1,
+ "yoka.hyogo.jp": 1,
+ "yokaichiba.chiba.jp": 1,
+ "yokawa.hyogo.jp": 1,
+ "yokkaichi.mie.jp": 1,
+ "yokohama.jp": 2,
+ "yokoshibahikari.chiba.jp": 1,
+ "yokosuka.kanagawa.jp": 1,
+ "yokote.akita.jp": 1,
+ "yokoze.saitama.jp": 1,
+ "yomitan.okinawa.jp": 1,
+ "yonabaru.okinawa.jp": 1,
+ "yonago.tottori.jp": 1,
+ "yonaguni.okinawa.jp": 1,
+ "yonezawa.yamagata.jp": 1,
+ "yono.saitama.jp": 1,
+ "yorii.saitama.jp": 1,
+ "york.museum": 1,
+ "yorkshire.museum": 1,
+ "yoro.gifu.jp": 1,
+ "yosemite.museum": 1,
+ "yoshida.saitama.jp": 1,
+ "yoshida.shizuoka.jp": 1,
+ "yoshikawa.saitama.jp": 1,
+ "yoshimi.saitama.jp": 1,
+ "yoshino.nara.jp": 1,
+ "yoshinogari.saga.jp": 1,
+ "yoshioka.gunma.jp": 1,
+ "yotsukaido.chiba.jp": 1,
+ "youth.museum": 1,
+ "yuasa.wakayama.jp": 1,
+ "yufu.oita.jp": 1,
+ "yugawa.fukushima.jp": 1,
+ "yugawara.kanagawa.jp": 1,
+ "yuki.ibaraki.jp": 1,
+ "yukuhashi.fukuoka.jp": 1,
+ "yura.wakayama.jp": 1,
+ "yurihonjo.akita.jp": 1,
+ "yusuhara.kochi.jp": 1,
+ "yusui.kagoshima.jp": 1,
+ "yuu.yamaguchi.jp": 1,
+ "yuza.yamagata.jp": 1,
+ "yuzawa.niigata.jp": 1,
+ "yuzhno-sakhalinsk.ru": 1,
+ "z.bg": 1,
+ "z.se": 1,
+ "za": 2,
+ "za.com": 1,
+ "za.net": 1,
+ "za.org": 1,
+ "zachpomor.pl": 1,
+ "zagan.pl": 1,
+ "zakopane.pl": 1,
+ "zama.kanagawa.jp": 1,
+ "zamami.okinawa.jp": 1,
+ "zao.miyagi.jp": 1,
+ "zaporizhzhe.ua": 1,
+ "zaporizhzhia.ua": 1,
+ "zarow.pl": 1,
+ "zentsuji.kagawa.jp": 1,
+ "zgora.pl": 1,
+ "zgorzelec.pl": 1,
+ "zgrad.ru": 1,
+ "zhitomir.ua": 1,
+ "zhytomyr.ua": 1,
+ "zj.cn": 1,
+ "zlg.br": 1,
+ "zm": 2,
+ "zoological.museum": 1,
+ "zoology.museum": 1,
+ "zp.ua": 1,
+ "zt.ua": 1,
+ "zushi.kanagawa.jp": 1,
+ "zw": 2,
+ "\u00e1k\u014boluokta.no": 1,
+ "\u00e1laheadju.no": 1,
+ "\u00e1lt\u00e1.no": 1,
+ "\u00e5fjord.no": 1,
+ "\u00e5krehamn.no": 1,
+ "\u00e5l.no": 1,
+ "\u00e5lesund.no": 1,
+ "\u00e5lg\u00e5rd.no": 1,
+ "\u00e5mli.no": 1,
+ "\u00e5mot.no": 1,
+ "\u00e5rdal.no": 1,
+ "\u00e5s.no": 1,
+ "\u00e5seral.no": 1,
+ "\u00e5snes.no": 1,
+ "\u00f8ksnes.no": 1,
+ "\u00f8rland.no": 1,
+ "\u00f8rskog.no": 1,
+ "\u00f8rsta.no": 1,
+ "\u00f8stre-toten.no": 1,
+ "\u00f8vre-eiker.no": 1,
+ "\u00f8yer.no": 1,
+ "\u00f8ygarden.no": 1,
+ "\u00f8ystre-slidre.no": 1,
+ "\u010d\u00e1hcesuolo.no": 1,
+ "\u0438\u043a\u043e\u043c.museum": 1,
+ "\u05d9\u05e8\u05d5\u05e9\u05dc\u05d9\u05dd.museum": 1,
+ "\u0627\u064a\u0631\u0627\u0646.ir": 1,
+ "\u0627\u06cc\u0631\u0627\u0646.ir": 1,
+ "\u4e2a\u4eba.hk": 1,
+ "\u500b\u4eba.hk": 1,
+ "\u516c\u53f8.cn": 1,
+ "\u516c\u53f8.hk": 1,
+ "\u5546\u696d.tw": 1,
+ "\u653f\u5e9c.hk": 1,
+ "\u654e\u80b2.hk": 1,
+ "\u6559\u80b2.hk": 1,
+ "\u7b87\u4eba.hk": 1,
+ "\u7d44\u7e54.hk": 1,
+ "\u7d44\u7e54.tw": 1,
+ "\u7d44\u7ec7.hk": 1,
+ "\u7db2\u7d61.cn": 1,
+ "\u7db2\u7d61.hk": 1,
+ "\u7db2\u7edc.hk": 1,
+ "\u7db2\u8def.tw": 1,
+ "\u7ec4\u7e54.hk": 1,
+ "\u7ec4\u7ec7.hk": 1,
+ "\u7f51\u7d61.hk": 1,
+ "\u7f51\u7edc.cn": 1,
+ "\u7f51\u7edc.hk": 1
+};
+
+/*!
+ * Parts of original code from ipv6.js
+ * Copyright 2011 Beau Gunderson
+ * Available under MIT license
+ */
+
+var RE_V4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|0x[0-9a-f][0-9a-f]?|0[0-7]{3})\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|0x[0-9a-f][0-9a-f]?|0[0-7]{3})$/i;
+var RE_V4_HEX = /^0x([0-9a-f]{8})$/i;
+var RE_V4_NUMERIC = /^[0-9]+$/;
+var RE_V4inV6 = /(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+
+var RE_BAD_CHARACTERS = /([^0-9a-f:])/i;
+var RE_BAD_ADDRESS = /([0-9a-f]{5,}|:{3,}|[^:]:$|^:[^:]$)/i;
+
+function isIPv4(address)
+{
+ if (RE_V4.test(address))
+ return true;
+ if (RE_V4_HEX.test(address))
+ return true;
+ if (RE_V4_NUMERIC.test(address))
+ return true;
+ return false;
+}
+
+function isIPv6(address)
+{
+ var a4addon = 0;
+ var address4 = address.match(RE_V4inV6);
+ if (address4)
+ {
+ var temp4 = address4[0].split('.');
+ for (var i = 0; i < 4; i++)
+ {
+ if (/^0[0-9]+/.test(temp4[i]))
+ return false;
+ }
+ address = address.replace(RE_V4inV6, '');
+ if (/[0-9]$/.test(address))
+ return false;
+
+ address = address + temp4.join(':');
+ a4addon = 2;
+ }
+
+ if (RE_BAD_CHARACTERS.test(address))
+ return false;
+
+ if (RE_BAD_ADDRESS.test(address))
+ return false;
+
+ function count(string, substring)
+ {
+ return (string.length - string.replace(new RegExp(substring,"g"), '').length) / substring.length;
+ }
+
+ var halves = count(address, '::');
+ if (halves == 1 && count(address, ':') <= 6 + 2 + a4addon)
+ return true;
+ if (halves == 0 && count(address, ':') == 7 + a4addon)
+ return true;
+ return false;
+}
+
+/**
+ * Returns base domain for specified host based on Public Suffix List.
+ */
+function getBaseDomain(/**String*/ hostname) /**String*/
+{
+ // remove trailing dot(s)
+ hostname = hostname.replace(/\.+$/, '');
+
+ // return IP address untouched
+ if (isIPv6(hostname) || isIPv4(hostname))
+ return hostname;
+
+ // decode punycode if exists
+ //if (hostname.indexOf('xn--') >= 0)
+ //{
+ // hostname = punycode.toUnicode(hostname);
+ //}
+
+ // search through PSL
+ var prevDomains = [];
+ var curDomain = hostname;
+ var nextDot = curDomain.indexOf('.');
+ var tld = 0;
+
+ while (true)
+ {
+ var suffix = publicSuffixes[curDomain];
+ if (typeof(suffix) != 'undefined')
+ {
+ tld = suffix;
+ break;
+ }
+
+ if (nextDot < 0)
+ {
+ tld = 1;
+ break;
+ }
+
+ prevDomains.push(curDomain.substring(0,nextDot));
+ curDomain = curDomain.substring(nextDot+1);
+ nextDot = curDomain.indexOf('.');
+ }
+
+ while (tld > 0 && prevDomains.length > 0)
+ {
+ curDomain = prevDomains.pop() + '.' + curDomain;
+ tld--;
+ }
+
+ return curDomain;
+}
+
+/**
+ * Checks whether a request is third party for the given document, uses
+ * information from the public suffix list to determine the effective domain
+ * name for the document.
+ */
+function isThirdParty(/**String*/ requestHost, /**String*/ documentHost)
+{
+ // Remove trailing dots
+ requestHost = requestHost.replace(/\.+$/, "");
+ documentHost = documentHost.replace(/\.+$/, "");
+
+ // Extract domain name - leave IP addresses unchanged, otherwise leave only base domain
+ var documentDomain = getBaseDomain(documentHost);
+ if (requestHost.length > documentDomain.length)
+ return (requestHost.substr(requestHost.length - documentDomain.length - 1) != "." + documentDomain);
+ else
+ return (requestHost != documentDomain);
+}
+
+/**
+ * Extracts host name from a URL.
+ */
+function extractHostFromURL(/**String*/ url)
+{
+ if (url && extractHostFromURL._lastURL == url)
+ return extractHostFromURL._lastDomain;
+
+ var host = "";
+ try
+ {
+ host = new URI(url).host;
+ }
+ catch (e)
+ {
+ // Keep the empty string for invalid URIs.
+ }
+
+ extractHostFromURL._lastURL = url;
+ extractHostFromURL._lastDomain = host;
+ return host;
+}
+
+/**
+ * Parses URLs and provides an interface similar to nsIURI in Gecko, see
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIURI.
+ * TODO: Make sure the parsing actually works the same as nsStandardURL.
+ * @constructor
+ */
+function URI(/**String*/ spec)
+{
+ this.spec = spec;
+ this._schemeEnd = spec.indexOf(":");
+ if (this._schemeEnd < 0)
+ throw new Error("Invalid URI scheme");
+
+ if (spec.substr(this._schemeEnd + 1, 2) != "//")
+ throw new Error("Unexpected URI structure");
+
+ this._hostPortStart = this._schemeEnd + 3;
+ this._hostPortEnd = spec.indexOf("/", this._hostPortStart);
+ if (this._hostPortEnd < 0)
+ throw new Error("Invalid URI host");
+
+ var authEnd = spec.indexOf("@", this._hostPortStart);
+ if (authEnd >= 0 && authEnd < this._hostPortEnd)
+ this._hostPortStart = authEnd + 1;
+
+ this._portStart = -1;
+ this._hostEnd = spec.indexOf("]", this._hostPortStart + 1);
+ if (spec[this._hostPortStart] == "[" && this._hostEnd >= 0 && this._hostEnd < this._hostPortEnd)
+ {
+ // The host is an IPv6 literal
+ this._hostStart = this._hostPortStart + 1;
+ if (spec[this._hostEnd + 1] == ":")
+ this._portStart = this._hostEnd + 2;
+ }
+ else
+ {
+ this._hostStart = this._hostPortStart;
+ this._hostEnd = spec.indexOf(":", this._hostStart);
+ if (this._hostEnd >= 0 && this._hostEnd < this._hostPortEnd)
+ this._portStart = this._hostEnd + 1;
+ else
+ this._hostEnd = this._hostPortEnd;
+ }
+}
+URI.prototype =
+{
+ spec: null,
+ get scheme()
+ {
+ return this.spec.substring(0, this._schemeEnd).toLowerCase();
+ },
+ get host()
+ {
+ return this.spec.substring(this._hostStart, this._hostEnd);
+ },
+ get asciiHost()
+ {
+ var host = this.host;
+ //if (/^[\x00-\x7F]+$/.test(host))
+ return host;
+ //else
+ // return punycode.toASCII(host);
+ },
+ get hostPort()
+ {
+ return this.spec.substring(this._hostPortStart, this._hostPortEnd);
+ },
+ get port()
+ {
+ if (this._portStart < 0)
+ return -1;
+ else
+ return parseInt(this.spec.substring(this._portStart, this._hostPortEnd), 10);
+ },
+ get path()
+ {
+ return this.spec.substring(this._hostPortEnd);
+ },
+ get prePath()
+ {
+ return this.spec.substring(0, this._hostPortEnd);
+ }
+};
+
+var RuleList = function( parentObj ) {
+
+ this._parentObj = parentObj;
+
+ // Runtime Rule object storage collection
+ this._collection = [];
+
+ this.createRule = function(rule, options) {
+
+ rule += ""; // force rule argument to be a string
+
+ // strip leading/trailing whitespace from rule
+ rule = rule.replace(/\s*$/,''); // rtrim
+ rule = rule.replace(/^\s*/,''); // ltrim
+
+ if(rule === "") {
+ return { 'id': 0, 'rule': null };
+ }
+
+ var ruleId = Math.floor( Math.random() * 1e15 );
+
+ // Sanitize options, if any
+
+ options = options || {};
+
+ var opts = {
+ 'includeDomains': options.includeDomains || [],
+ 'excludeDomains': options.excludeDomains || [],
+ 'resources': options.resources || 0xFFFFFFFF,
+ 'thirdParty': options.thirdParty !== undefined ? options.thirdParty : null
+ };
+
+ // Process options and append to rule argument
+
+ var filterOptions = [];
+
+ var includeDomainsStr = "";
+ var excludeDomainsStr = "";
+
+ if(opts.includeDomains && opts.includeDomains.length > 0) {
+
+ for(var i = 0, l = opts.includeDomains.length; i < l; i++) {
+ if(includeDomainsStr.length > 0) includeDomainsStr += "|"; // add domain seperator (pipe)
+ includeDomainsStr += opts.includeDomains[i];
+ }
+
+ }
+
+ if(opts.excludeDomains && opts.excludeDomains.length > 0) {
+
+ for(var i = 0, l = opts.excludeDomains.length; i < l; i++) {
+ if(excludeDomainsStr.length > 0 || includeDomainsStr.length > 0) excludeDomainsStr += "|"; // add domain seperator (pipe)
+ excludeDomainsStr += "~" + opts.excludeDomains[i];
+ }
+
+ }
+
+ if(includeDomainsStr.length > 0 || excludeDomainsStr.length > 0) {
+
+ var domainsStr = "domain=" + includeDomainsStr + excludeDomainsStr;
+
+ filterOptions.push(domainsStr);
+
+ }
+
+ if(opts.resources && opts.resources !== 0xFFFFFFFF) {
+
+ var typeMap = {
+ 1: "other",
+ 2: "script",
+ 4: "image",
+ 8: "stylesheet",
+ 16: "object",
+ 32: "subdocument",
+ 64: "document",
+ 128: "refresh",
+ 2048: "xmlhttprequest",
+ 4096: "object_subrequest",
+ 16384: "media",
+ 32768: "font"
+ };
+
+ var resourcesListStr = "";
+
+ for(var i = 0, l = 31; i < l; i ++) {
+ if(((opts.resources >> i) % 2 != 0) === true) {
+ var typeStr = typeMap[ Math.pow(2, i) ];
+ if(typeStr) {
+ if(resourcesListStr.length > 0) resourcesListStr += ",";
+ resourcesListStr += typeStr;
+ }
+ }
+ }
+
+ if(resourcesListStr.length > 0) {
+ filterOptions.push(resourcesListStr);
+ }
+
+ }
+
+ if(opts.thirdParty === true) {
+ filterOptions.push("third-party");
+ } else if (opts.thirdParty === false) {
+ filterOptions.push("~third-party");
+ }
+
+ if(filterOptions.length > 0) {
+ rule += "$";
+
+ for(var i = 0, l = filterOptions.length; i < l; i++) {
+ if(i !== 0) rule += ","; // add filter options seperator (comma)
+ rule += filterOptions[i];
+ }
+ }
+
+ return { 'id': ruleId, 'rule': rule };
+
+ }
+
+ this.addRule = function( ruleObj ) {
+
+ // Parse rule to a Filter object
+ var filter = this._parentObj.Filter.fromText( ruleObj['rule'] );
+
+ // Add rule's filter object to AdBlock FilterStorage
+ this._parentObj.FilterStorage.addFilter(filter);
+
+ // Add rule to current RuleList collection
+ this._collection.push({
+ 'id': ruleObj['id'],
+ 'filter': filter
+ });
+
+ }
+
+ this.removeRule = function( ruleId ) {
+
+ for(var i = 0, l = this._collection.length; i < l; i++) {
+
+ if( this._collection[i]['id'] && this._collection[i]['id'] == ruleId ) {
+
+ // Remove rule's filter object from AdBlock FilterStorage
+ this._parentObj.FilterStorage.removeFilter(this._collection[i]['filter']);
+
+ // Remove rule from current RuleList collection
+ this._collection.splice(i);
+
+ break;
+ }
+ }
+
+ }
+
+};
+
+RuleList.prototype.add = function( rule, options ) {
+
+ var ruleObj = this.createRule(rule, options);
+
+ if(ruleObj['rule'] !== null) {
+ this.addRule(ruleObj);
+ }
+
+ return ruleObj['id'];
+
+};
+
+RuleList.prototype.remove = function( ruleId ) {
+
+ this.removeRule( ruleId );
+
+};
+
+var BlockRuleList = function( parentObj ) {
+
+ RuleList.call(this, parentObj);
+
+};
+
+BlockRuleList.prototype = Object.create( RuleList.prototype );
+
+var AllowRuleList = function( parentObj ) {
+
+ RuleList.call(this, parentObj);
+
+};
+
+AllowRuleList.prototype = Object.create( RuleList.prototype );
+
+AllowRuleList.prototype.add = function( rule, options ) {
+
+ var ruleObj = this.createRule(rule, options);
+
+ if(ruleObj['rule'] !== null) {
+
+ // Add exclude pattern to rule (@@)
+ ruleObj['rule'] = "@@" + ruleObj['rule'];
+
+ this.addRule(ruleObj);
+
+ }
+
+ return ruleObj['id'];
+
+};
+
+var UrlFilterManager = function() {
+
+ OEventTarget.call(this);
+
+ // Add rule list management stubs
+ this.block = new BlockRuleList( this );
+ this.allow = new AllowRuleList( this );
+
+ // event queue manager
+ this.eventQueue = {
+ /*
+ tabId: [
+ 'ready': false,
+ 'contentblocked': [ { eventdetails }, { eventDetails }, { eventDetails } ],
+ 'contentunblocked': [ { eventdetails }, { eventDetails }, { eventDetails } ],
+ 'contentallowed': [ { eventdetails }, { eventDetails }, { eventDetails } ]
+ ],
+ ...
+ */
+ };
+
+ // https://github.com/adblockplus/adblockpluschrome/blob/master/background.js
+
+ with(require("filterClasses")) {
+ this.Filter = Filter;
+ this.RegExpFilter = RegExpFilter;
+ this.BlockingFilter = BlockingFilter;
+ this.WhitelistFilter = WhitelistFilter;
+ }
+
+ with(require("subscriptionClasses")) {
+ this.Subscription = Subscription;
+ //this.DownloadableSubscription = DownloadableSubscription;
+ }
+
+ this.FilterStorage = require("filterStorage").FilterStorage;
+
+ this.defaultMatcher = require("matcher").defaultMatcher;
+
+ // https://github.com/adblockplus/adblockpluschrome/blob/master/webrequest.js
+
+ var self = this;
+
+ var frames = {};
+
+ function recordFrame(tabId, frameId, parentFrameId, frameUrl) {
+ if (!(tabId in frames))
+ frames[tabId] = {};
+ frames[tabId][frameId] = {url: frameUrl, parent: parentFrameId};
+ }
+
+ function getFrameData(tabId, frameId) {
+ if (tabId in frames && frameId in frames[tabId])
+ return frames[tabId][frameId];
+ else if (frameId > 0 && tabId in frames && 0 in frames[tabId])
+ {
+ // We don't know anything about javascript: or data: frames, use top frame
+ return frames[tabId][0];
+ }
+ return null;
+ }
+
+ function getFrameUrl(tabId, frameId) {
+ var frameData = getFrameData(tabId, frameId);
+ return (frameData ? frameData.url : null);
+ }
+
+ function forgetTab(tabId) {
+ delete frames[tabId];
+ }
+
+ function checkRequest(type, tabId, url, frameId) {
+ if (isFrameWhitelisted(tabId, frameId))
+ return false;
+
+ var documentUrl = getFrameUrl(tabId, frameId);
+ if (!documentUrl)
+ return false;
+
+ var requestHost = extractHostFromURL(url);
+ var documentHost = extractHostFromURL(documentUrl);
+ var thirdParty = isThirdParty(requestHost, documentHost);
+
+ return self.defaultMatcher.matchesAny(url, type, documentHost, thirdParty);
+ }
+
+ function isFrameWhitelisted(tabId, frameId, type) {
+ var parent = frameId;
+ var parentData = getFrameData(tabId, parent);
+ while (parentData)
+ {
+ var frame = parent;
+ var frameData = parentData;
+
+ parent = frameData.parent;
+ parentData = getFrameData(tabId, parent);
+
+ var frameUrl = frameData.url;
+ var parentUrl = (parentData ? parentData.url : frameUrl);
+ if ("keyException" in frameData || isWhitelisted(frameUrl, parentUrl, type))
+ return true;
+ }
+ return false;
+ }
+
+ function isWhitelisted(url, parentUrl, type)
+ {
+ // Ignore fragment identifier
+ var index = url.indexOf("#");
+ if (index >= 0)
+ url = url.substring(0, index);
+
+ var result = self.defaultMatcher.matchesAny(url, type || "DOCUMENT", extractHostFromURL(parentUrl || url), false);
+ return (result instanceof self.WhitelistFilter ? result : null);
+ }
+
+ // Parse a single web request url and decide whether we should block or not
+ function onBeforeRequest(details) {
+
+ if (details.tabId == -1) {
+ return {};
+ }
+
+ var type = details.type;
+
+ if (type == "main_frame" || type == "sub_frame") {
+ recordFrame(details.tabId, details.frameId, details.parentFrameId, details.url);
+ }
+
+ // Type names match Mozilla's with main_frame and sub_frame being the only exceptions.
+ if (type == "sub_frame") {
+ type = "SUBDOCUMENT";
+ } else if (type == "main_frame") {
+ type = "DOCUMENT";
+ } else {
+ type = (type + "").toUpperCase();
+ }
+
+ var frame = (type != "SUBDOCUMENT" ? details.frameId : details.parentFrameId);
+
+ var filter = checkRequest(type, details.tabId, details.url, frame);
+
+ if (filter instanceof self.BlockingFilter) {
+
+ var msgData = {
+ "action": "___O_urlfilter_contentblocked",
+ "data": {
+ // send enough data so that we can fire the event in the injected script
+ "url": details.url
+ }
+ };
+
+ // Broadcast contentblocked event control message (i.e. beginning with '___O_')
+ // towards the tab matching the details.tabId value
+ // (but delay it until the content script is loaded!)
+ if(self.eventQueue[details.tabId] !== undefined && self.eventQueue[details.tabId].ready === true) {
+
+ // tab is already online so send contentblocked messages
+ chrome.tabs.sendMessage(
+ details.tabId,
+ msgData,
+ function() {}
+ );
+
+ } else {
+
+ // queue up this event
+ if(self.eventQueue[details.tabId] === undefined) {
+ self.eventQueue[details.tabId] = { 'ready': false, 'contentblocked': [], 'contentunblocked': [], 'contentallowed': [] };
+ }
+
+ self.eventQueue[details.tabId]['contentblocked'].push( msgData );
+
+ }
+
+ return { cancel: true };
+
+ } else if (filter instanceof self.WhitelistFilter) {
+
+ var msgData = {
+ "action": "___O_urlfilter_contentunblocked",
+ "data": {
+ // send enough data so that we can fire the event in the injected script
+ "url": details.url
+ }
+ };
+
+ // Broadcast contentblocked event control message (i.e. beginning with '___O_')
+ // towards the tab matching the details.tabId value
+ // (but delay it until the content script is loaded!)
+ if(self.eventQueue[details.tabId] !== undefined && self.eventQueue[details.tabId].ready === true) {
+
+ // tab is already online so send contentblocked messages
+ chrome.tabs.sendMessage(
+ details.tabId,
+ msgData,
+ function() {}
+ );
+
+ } else {
+
+ // queue up this event
+ if(self.eventQueue[details.tabId] === undefined) {
+ self.eventQueue[details.tabId] = { 'ready': false, 'contentblocked': [], 'contentunblocked': [], 'contentallowed': [] };
+ }
+
+ self.eventQueue[details.tabId]['contentunblocked'].push( msgData );
+
+ }
+
+ return {};
+
+ } else {
+
+ var msgData = {
+ "action": "___O_urlfilter_contentallowed",
+ "data": {
+ // send enough data so that we can fire the event in the injected script
+ "url": details.url
+ }
+ };
+
+ // Broadcast contentblocked event control message (i.e. beginning with '___O_')
+ // towards the tab matching the details.tabId value
+ // (but delay it until the content script is loaded!)
+ if(self.eventQueue[details.tabId] !== undefined && self.eventQueue[details.tabId].ready === true) {
+
+ // tab is already online so send contentblocked messages
+ chrome.tabs.sendMessage(
+ details.tabId,
+ msgData,
+ function() {}
+ );
+
+ } else {
+
+ // queue up this event
+ if(self.eventQueue[details.tabId] === undefined) {
+ self.eventQueue[details.tabId] = { 'ready': false, 'contentblocked': [], 'contentunblocked': [], 'contentallowed': [] };
+ }
+
+ self.eventQueue[details.tabId]['contentallowed'].push( msgData );
+
+ }
+
+ return {};
+
+ }
+ }
+
+ // Listen for webRequest beforeRequest events and block
+ // if a rule matches in the associated block RuleList
+ chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: [ "http://*/*", "https://*/*" ] }, [ "blocking" ]);
+
+ // Wait for tab to add event listeners for urlfilter and then drain queued up events to that tab
+ OEX.addEventListener('controlmessage', function( msg ) {
+
+ if( !msg.data || !msg.data.action ) {
+ return;
+ }
+
+ if( msg.data.action != '___O_urlfilter_DRAINQUEUE' || !msg.data.eventType ) {
+ return;
+ }
+
+ // Drain queued events belonging to this tab
+ var tabId = msg.source.tabId;
+
+ if( self.eventQueue[tabId] !== undefined ) {
+ self.eventQueue[tabId].ready = true; // set to resolved (true)
+
+ var eventQueue = self.eventQueue[tabId][ msg.data.eventType ];
+
+ for(var i = 0, l = eventQueue.length; i < l; i++) {
+
+ msg.source.postMessage(eventQueue[i]);
+
+ }
+
+ self.eventQueue[tabId][ msg.data.eventType ] = []; // reset event queue
+
+ }
+
+ });
+
+ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+
+ switch(changeInfo.status) {
+
+ case 'loading':
+
+ // kill previous events queue
+ if(self.eventQueue[tabId] === undefined) {
+ self.eventQueue[tabId] = { 'ready': false, 'contentblocked': [], 'contentunblocked': [], 'contentallowed': [] };
+ } else {
+ self.eventQueue[tabId].ready = false;
+ }
+
+ break;
+
+ case 'complete':
+
+ if(self.eventQueue[tabId] !== undefined && self.eventQueue[tabId].ready !== undefined ) {
+
+ self.eventQueue[tabId].ready = true;
+
+ }
+
+ break;
+
+ }
+
+ });
+
+};
+
+UrlFilterManager.prototype = Object.create( OEventTarget.prototype );
+
+// URL Filter Resource Types (bit-mask values)
+
+UrlFilterManager.prototype.RESOURCE_OTHER = 0x00000001; // 1
+UrlFilterManager.prototype.RESOURCE_SCRIPT = 0x00000002; // 2
+UrlFilterManager.prototype.RESOURCE_IMAGE = 0x00000004; // 4
+UrlFilterManager.prototype.RESOURCE_STYLESHEET = 0x00000008; // 8
+UrlFilterManager.prototype.RESOURCE_OBJECT = 0x00000010; // 16
+UrlFilterManager.prototype.RESOURCE_SUBDOCUMENT = 0x00000020; // 32
+UrlFilterManager.prototype.RESOURCE_DOCUMENT = 0x00000040; // 64
+UrlFilterManager.prototype.RESOURCE_REFRESH = 0x00000080; // 128
+UrlFilterManager.prototype.RESOURCE_XMLHTTPREQUEST = 0x00000800; // 2048
+UrlFilterManager.prototype.RESOURCE_OBJECT_SUBREQUEST = 0x00001000; // 4096
+UrlFilterManager.prototype.RESOURCE_MEDIA = 0x00004000; // 16384
+UrlFilterManager.prototype.RESOURCE_FONT = 0x00008000; // 32768
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('webRequest') != -1 && manifest.permissions.indexOf('webRequestBlocking') != -1 ) {
+
+ OEX.urlfilter = OEX.urlfilter || new UrlFilterManager();
+
+}
+
+if (global.opera) {
+ isReady = true;
+
+ // Make scripts also work in Opera <= version 12
+ opera.isReady = function(fn) {
+ fn.call(opera);
+
+ // Run delayed events (if any)
+ for(var i = 0, l = _delayedExecuteEvents.length; i < l; i++) {
+ var o = _delayedExecuteEvents[i];
+ o.target[o.methodName].apply(o.target, o.args);
+ }
+ _delayedExecuteEvents = [];
+ };
+
+} else {
+
+ opera.isReady = (function() {
+
+ var fns = {
+ "isready": [],
+ "readystatechange": [],
+ "domcontentloaded": [],
+ "load": []
+ };
+
+ var hasFired_DOMContentLoaded = false,
+ hasFired_Load = false;
+
+ // If we already missed DOMContentLoaded or Load events firing, record that now...
+ if(global.document.readyState === "interactive") {
+ hasFired_DOMContentLoaded = true;
+ }
+ if(global.document.readyState === "complete") {
+ hasFired_DOMContentLoaded = true;
+ hasFired_Load = true;
+ }
+
+ // ...otherwise catch DOMContentLoaded and Load events when they happen and set the same flag.
+ global.document.addEventListener("DOMContentLoaded", function handle_DomContentLoaded() {
+ hasFired_DOMContentLoaded = true;
+ global.document.removeEventListener("DOMContentLoaded", handle_DomContentLoaded, true);
+ }, true);
+ global.addEventListener("load", function handle_Load() {
+ hasFired_Load = true;
+ global.removeEventListener("load", handle_Load, true);
+ }, true);
+
+ // Catch and fire readystatechange events when they happen
+ global.document.addEventListener("readystatechange", function(event) {
+ event.stopImmediatePropagation();
+ event.stopPropagation();
+ if( global.document.readyState !== 'interactive' && global.document.readyState !== 'complete' ) {
+ fireEvent('readystatechange', global.document);
+ } else {
+ global.document.readyState = 'loading';
+ }
+ }, true);
+
+ // Take over handling of document.readyState via our own load bootstrap code below
+ var _readyState = (hasFired_DOMContentLoaded || hasFired_Load) ? global.document.readyState : "uninitialized";
+ global.document.__defineSetter__('readyState', function(val) { _readyState = val; });
+ global.document.__defineGetter__('readyState', function() { return _readyState; });
+
+ function interceptAddEventListener(target, _name) {
+
+ var _target = target.addEventListener;
+
+ // Replace addEventListener for given target
+ target.addEventListener = function(name, fn, usecapture) {
+ name = name + ""; // force event name to type string
+
+ if (name.toLowerCase() === _name.toLowerCase()) {
+ if (fn === undefined || fn === null ||
+ Object.prototype.toString.call(fn) !== "[object Function]") {
+ return;
+ }
+
+ if (isReady) {
+ fn.call(global);
+ } else {
+ fns[_name.toLowerCase()].push(fn);
+ }
+ } else {
+ // call standard addEventListener method on target
+ _target.call(target, name, fn, usecapture);
+ }
+ };
+
+ // Replace target.on[_name] with custom setter function
+ target.__defineSetter__("on" + _name.toLowerCase(), function( fn ) {
+ // call code block just created above...
+ target.addEventListener(_name.toLowerCase(), fn, false);
+ });
+
+ }
+
+ interceptAddEventListener(global, 'load');
+ interceptAddEventListener(global.document, 'domcontentloaded');
+ interceptAddEventListener(global, 'domcontentloaded'); // handled bubbled DOMContentLoaded events
+ interceptAddEventListener(global.document, 'readystatechange');
+
+ function fireEvent(name, target, props) {
+ var evtName = name.toLowerCase();
+
+ // Role a standard object as the Event since we really need
+ // to set the target + other unsettable properties on the
+ // isReady events
+
+ var evt = props || {};
+
+ evt.type = name;
+
+ if(!evt.target) evt.target = global;
+ if(!evt.currentTarget) evt.currentTarget = evt.target;
+ if(!evt.srcElement) evt.srcElement = evt.target;
+
+ if(evt.bubbles !== true) evt.bubbles = false;
+ if(evt.cancelable !== true) evt.cancelable = false;
+
+ if(!evt.timeStamp) evt.timeStamp = 0;
+
+ for (var i = 0, len = fns[evtName].length; i < len; i++) {
+ fns[evtName][i].call(target, evt);
+ }
+ }
+
+ function ready() {
+ global.setTimeout(function() {
+
+ if (isReady) {
+ return;
+ }
+
+ // Handle queued opera 'isReady' event functions
+ for (var i = 0, len = fns['isready'].length; i < len; i++) {
+ fns['isready'][i].call(global);
+ }
+ fns['isready'] = []; // clear
+
+ var domContentLoadedTimeoutOverride = new Date().getTime() + 120000;
+
+ // Synthesize and fire the document domcontentloaded event
+ (function fireDOMContentLoaded() {
+
+ var currentTime = new Date().getTime();
+
+ // Check for hadFired_Load in case we missed DOMContentLoaded
+ // event, in which case, we syntesize DOMContentLoaded here
+ // (always synthesized in Chromium Content Scripts)
+ if (hasFired_DOMContentLoaded || hasFired_Load || currentTime >= domContentLoadedTimeoutOverride) {
+
+ global.document.readyState = 'interactive';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('domcontentloaded', global.document, { bubbles: true }); // indicate that event bubbles
+
+ if(currentTime >= domContentLoadedTimeoutOverride) {
+ console.warn('document.domcontentloaded event fired on check timeout');
+ }
+
+ var loadTimeoutOverride = new Date().getTime() + 120000;
+
+ // Synthesize and fire the window load event
+ // after the domcontentloaded event has been
+ // fired
+ (function fireLoad() {
+
+ var currentTime = new Date().getTime();
+
+ if (hasFired_Load || currentTime >= loadTimeoutOverride) {
+
+ global.document.readyState = 'complete';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('load', global);
+
+ if(currentTime >= loadTimeoutOverride) {
+ console.warn('window.load event fired on check timeout');
+ }
+
+ // Run delayed events (if any)
+ for(var i = 0, l = _delayedExecuteEvents.length; i < l; i++) {
+ var o = _delayedExecuteEvents[i];
+ o.target[o.methodName].apply(o.target, o.args);
+ }
+ _delayedExecuteEvents = [];
+
+ } else {
+ global.setTimeout(function() {
+ fireLoad();
+ }, 50);
+ }
+
+ })();
+
+ } else {
+ global.setTimeout(function() {
+ fireDOMContentLoaded();
+ }, 50);
+ }
+
+ })();
+
+ isReady = true;
+
+ }, 0);
+ }
+
+ var holdTimeoutOverride = new Date().getTime() + 240000;
+
+ (function holdReady() {
+
+ var currentTime = new Date().getTime();
+
+ if (currentTime >= holdTimeoutOverride) {
+ // All scripts now ready to be executed: TIMEOUT override
+ console.warn('opera.isReady check timed out');
+ hasFired_Load = true; // override
+ ready();
+ return;
+ }
+
+ for (var i in deferredComponentsLoadStatus) {
+ if (deferredComponentsLoadStatus[i] !== true) {
+ // spin the loop until everything is working
+ // or we receive a timeout override (handled
+ // in next loop, above)
+ global.setTimeout(function() {
+ holdReady();
+ }, 20);
+ return;
+ }
+ }
+
+ // All scripts now ready to be executed
+ ready();
+
+ })();
+
+ return function(fn) {
+ // if the Library is already ready,
+ // execute the function immediately.
+ // otherwise, queue it up until isReady
+ if (isReady) {
+ fn.call(global);
+ } else {
+ fns['isready'].push(fn);
+ }
+ }
+ })();
+
+}
+
+opera.isReady(function() {
+
+ // Rewrite in-line event handlers (eg. for a sub-set of common standard events)
+
+ document.addEventListener('DOMContentLoaded', function(e) {
+
+ var selectors = ['load', 'beforeunload', 'unload', 'click', 'dblclick', 'mouseover', 'mousemove',
+ 'mousedown', 'mouseup', 'mouseout', 'keydown', 'keypress', 'keyup', 'blur', 'focus'];
+
+ for(var i = 0, l = selectors.length; i < l; i++) {
+ var els = document.querySelectorAll('[on' + selectors[i] + ']');
+ for(var j = 0, k = els.length; j < k; j++) {
+ var fn = new Function('e', els[j].getAttribute('on' + selectors[i]));
+ var target = els[j];
+ if(selectors[i].indexOf('load') > -1 && els[j] === document.body) {
+ target = window;
+ }
+
+ els[j].removeAttribute('on' + selectors[i]);
+ target.addEventListener(selectors[i], fn, true);
+ }
+ }
+
+ }, false);
+
+});
+
+ // Make API available on the window DOM object
+ global.opera = opera;
+
+})( window );
\ No newline at end of file
diff --git a/oex_shim/operaextensions_injectedscript.js b/oex_shim/operaextensions_injectedscript.js
new file mode 100644
index 0000000..a0fa694
--- /dev/null
+++ b/oex_shim/operaextensions_injectedscript.js
@@ -0,0 +1,1333 @@
+!(function( global ) {
+
+ var Opera = function() {};
+
+ Opera.prototype.REVISION = '1';
+
+ Opera.prototype.version = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.buildNumber = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.postError = function( str ) {
+ console.log( str );
+ };
+
+ var opera = global.opera || new Opera();
+
+ var manifest = chrome.app.getDetails(); // null in injected scripts / popups
+
+ navigator.browserLanguage=navigator.language; //Opera defines both, some extensions use the former
+
+ var isReady = false;
+
+ var _delayedExecuteEvents = [
+ // Example:
+ // { 'target': opera.extension, 'methodName': 'message', 'args': event }
+ ];
+
+ // Pick the right base URL for new tab generation
+ var newTab_BaseURL = 'data:text/html,Loading... ';
+
+ function addDelayedEvent(target, methodName, args) {
+ if(isReady) {
+ target[methodName].apply(target, args);
+ } else {
+ _delayedExecuteEvents.push({
+ "target": target,
+ "methodName": methodName,
+ "args": args
+ });
+ }
+ };
+
+// Used to trigger opera.isReady() functions
+var deferredComponentsLoadStatus = {
+ 'WIDGET_API_LOADED': false,
+ 'WIDGET_PREFERENCES_LOADED': false
+ // ...etc
+};
+
+// Events to delay until window 'load' event has been
+// fired by opera.isReady() below
+var delayedExecuteEvents = [
+ // Example:
+ // { 'target': opera.extension, 'eventName': 'message', 'eventObj': event }
+];
+
+/**
+ * rsvp.js
+ *
+ * Author: Tilde, Inc.
+ * URL: https://github.com/tildeio/rsvp.js
+ * Licensed under MIT License
+ *
+ * Customized for use in operaextensions.js
+ * By: Rich Tibbett
+ */
+
+ var exports = {};
+ var browserGlobal = (typeof window !== 'undefined') ? window : {};
+
+ var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var async;
+
+ if (typeof process !== 'undefined' &&
+ {}.toString.call(process) === '[object process]') {
+ async = function(callback, binding) {
+ process.nextTick(function() {
+ callback.call(binding);
+ });
+ };
+ } else if (MutationObserver) {
+ var queue = [];
+
+ var observer = new MutationObserver(function() {
+ var toProcess = queue.slice();
+ queue = [];
+
+ toProcess.forEach(function(tuple) {
+ var callback = tuple[0], binding = tuple[1];
+ callback.call(binding);
+ });
+ });
+
+ var element = document.createElement('div');
+ observer.observe(element, { attributes: true });
+
+ async = function(callback, binding) {
+ queue.push([callback, binding]);
+ element.setAttribute('drainQueue', 'drainQueue');
+ };
+ } else {
+ async = function(callback, binding) {
+ setTimeout(function() {
+ callback.call(binding);
+ }, 1);
+ };
+ }
+
+ exports.async = async;
+
+ var Event = exports.Event = function(type, options) {
+ this.type = type;
+
+ for (var option in options) {
+ if (!options.hasOwnProperty(option)) { continue; }
+
+ this[option] = options[option];
+ }
+ };
+
+ var indexOf = function(callbacks, callback) {
+ for (var i=0, l=callbacks.length; i= domContentLoadedTimeoutOverride) {
+
+ global.document.readyState = 'interactive';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('domcontentloaded', global.document, { bubbles: true }); // indicate that event bubbles
+
+ if(currentTime >= domContentLoadedTimeoutOverride) {
+ console.warn('document.domcontentloaded event fired on check timeout');
+ }
+
+ var loadTimeoutOverride = new Date().getTime() + 120000;
+
+ // Synthesize and fire the window load event
+ // after the domcontentloaded event has been
+ // fired
+ (function fireLoad() {
+
+ var currentTime = new Date().getTime();
+
+ if (hasFired_Load || currentTime >= loadTimeoutOverride) {
+
+ global.document.readyState = 'complete';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('load', global);
+
+ if(currentTime >= loadTimeoutOverride) {
+ console.warn('window.load event fired on check timeout');
+ }
+
+ // Run delayed events (if any)
+ for(var i = 0, l = _delayedExecuteEvents.length; i < l; i++) {
+ var o = _delayedExecuteEvents[i];
+ o.target[o.methodName].apply(o.target, o.args);
+ }
+ _delayedExecuteEvents = [];
+
+ } else {
+ global.setTimeout(function() {
+ fireLoad();
+ }, 50);
+ }
+
+ })();
+
+ } else {
+ global.setTimeout(function() {
+ fireDOMContentLoaded();
+ }, 50);
+ }
+
+ })();
+
+ isReady = true;
+
+ }, 0);
+ }
+
+ var holdTimeoutOverride = new Date().getTime() + 240000;
+
+ (function holdReady() {
+
+ var currentTime = new Date().getTime();
+
+ if (currentTime >= holdTimeoutOverride) {
+ // All scripts now ready to be executed: TIMEOUT override
+ console.warn('opera.isReady check timed out');
+ hasFired_Load = true; // override
+ ready();
+ return;
+ }
+
+ for (var i in deferredComponentsLoadStatus) {
+ if (deferredComponentsLoadStatus[i] !== true) {
+ // spin the loop until everything is working
+ // or we receive a timeout override (handled
+ // in next loop, above)
+ global.setTimeout(function() {
+ holdReady();
+ }, 20);
+ return;
+ }
+ }
+
+ // All scripts now ready to be executed
+ ready();
+
+ })();
+
+ return function(fn) {
+ // if the Library is already ready,
+ // execute the function immediately.
+ // otherwise, queue it up until isReady
+ if (isReady) {
+ fn.call(global);
+ } else {
+ fns['isready'].push(fn);
+ }
+ }
+ })();
+
+}
+
+ // Make API available on the window DOM object
+ global.opera = opera;
+
+})( window );
\ No newline at end of file
diff --git a/oex_shim/operaextensions_popup.js b/oex_shim/operaextensions_popup.js
new file mode 100644
index 0000000..2bd9486
--- /dev/null
+++ b/oex_shim/operaextensions_popup.js
@@ -0,0 +1,1137 @@
+!(function( global ) {
+
+ var Opera = function() {};
+
+ Opera.prototype.REVISION = '1';
+
+ Opera.prototype.version = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.buildNumber = function() {
+ return this.REVISION;
+ };
+
+ Opera.prototype.postError = function( str ) {
+ console.log( str );
+ };
+
+ var opera = global.opera || new Opera();
+
+ var manifest = chrome.app.getDetails(); // null in injected scripts / popups
+
+ navigator.browserLanguage=navigator.language; //Opera defines both, some extensions use the former
+
+ var isReady = false;
+
+ var _delayedExecuteEvents = [
+ // Example:
+ // { 'target': opera.extension, 'methodName': 'message', 'args': event }
+ ];
+
+ // Pick the right base URL for new tab generation
+ var newTab_BaseURL = 'data:text/html,Loading... ';
+
+ function addDelayedEvent(target, methodName, args) {
+ if(isReady) {
+ target[methodName].apply(target, args);
+ } else {
+ _delayedExecuteEvents.push({
+ "target": target,
+ "methodName": methodName,
+ "args": args
+ });
+ }
+ };
+
+// Used to trigger opera.isReady() functions
+var deferredComponentsLoadStatus = {
+ // ...etc
+};
+
+// Events to delay until window 'load' event has been
+// fired by opera.isReady() below
+var delayedExecuteEvents = [
+ // Example:
+ // { 'target': opera.extension, 'eventName': 'message', 'eventObj': event }
+];
+
+/**
+ * rsvp.js
+ *
+ * Author: Tilde, Inc.
+ * URL: https://github.com/tildeio/rsvp.js
+ * Licensed under MIT License
+ *
+ * Customized for use in operaextensions.js
+ * By: Rich Tibbett
+ */
+
+ var exports = {};
+ var browserGlobal = (typeof window !== 'undefined') ? window : {};
+
+ var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var async;
+
+ if (typeof process !== 'undefined' &&
+ {}.toString.call(process) === '[object process]') {
+ async = function(callback, binding) {
+ process.nextTick(function() {
+ callback.call(binding);
+ });
+ };
+ } else if (MutationObserver) {
+ var queue = [];
+
+ var observer = new MutationObserver(function() {
+ var toProcess = queue.slice();
+ queue = [];
+
+ toProcess.forEach(function(tuple) {
+ var callback = tuple[0], binding = tuple[1];
+ callback.call(binding);
+ });
+ });
+
+ var element = document.createElement('div');
+ observer.observe(element, { attributes: true });
+
+ async = function(callback, binding) {
+ queue.push([callback, binding]);
+ element.setAttribute('drainQueue', 'drainQueue');
+ };
+ } else {
+ async = function(callback, binding) {
+ setTimeout(function() {
+ callback.call(binding);
+ }, 1);
+ };
+ }
+
+ exports.async = async;
+
+ var Event = exports.Event = function(type, options) {
+ this.type = type;
+
+ for (var option in options) {
+ if (!options.hasOwnProperty(option)) { continue; }
+
+ this[option] = options[option];
+ }
+ };
+
+ var indexOf = function(callbacks, callback) {
+ for (var i=0, l=callbacks.length; i 1 ) {
+ t -= 1;
+ }
+ if ( t < 1 / 6 ) {
+ return p + ( q - p ) * 6 * t;
+ }
+ if ( t < 1 / 2 ) {
+ return q;
+ }
+ if ( t < 2 / 3 ) {
+ return p + ( q - p ) * ( 2 / 3 - t ) * 6;
+ }
+ return p;
+ };
+
+ var toRGB = {
+ rgb: function( bits ) {
+ return [ bits[1], bits[2], bits[3], bits[4] || 1 ];
+ },
+ hsl: function( bits ) {
+ var hsl = {
+ h : parseInt( bits[ 1 ], 10 ) % 360 / 360,
+ s : parseInt( bits[ 2 ], 10 ) % 101 / 100,
+ l : parseInt( bits[ 3 ], 10 ) % 101 / 100,
+ a : bits[4] || 1
+ };
+
+ if ( hsl.s === 0 ) {
+ return [ hsl.l, hsl.l, hsl.l ];
+ }
+
+ var q = hsl.l < 0.5 ? hsl.l * ( 1 + hsl.s ) : hsl.l + hsl.s - hsl.l * hsl.s;
+ var p = 2 * hsl.l - q;
+
+ return [
+ ( hueToRgb( p, q, hsl.h + 1 / 3 ) * 256 ).toFixed( 0 ),
+ ( hueToRgb( p, q, hsl.h ) * 256 ).toFixed( 0 ),
+ ( hueToRgb( p, q, hsl.h - 1 / 3 ) * 256 ).toFixed( 0 ),
+ hsl.a
+ ];
+ },
+ hsv: function( bits ) {
+ var rgb = {},
+ hsv = {
+ h : parseInt( bits[ 1 ], 10 ) % 360 / 360,
+ s : parseInt( bits[ 2 ], 10 ) % 101 / 100,
+ v : parseInt( bits[ 3 ], 10 ) % 101 / 100
+ },
+ i = Math.floor( hsv.h * 6 ),
+ f = hsv.h * 6 - i,
+ p = hsv.v * ( 1 - hsv.s ),
+ q = hsv.v * ( 1 - f * hsv.s ),
+ t = hsv.v * ( 1 - ( 1 - f ) * hsv.s );
+
+ switch( i % 6 ) {
+ case 0:
+ rgb.r = hsv.v; rgb.g = t; rgb.b = p;
+ break;
+ case 1:
+ rgb.r = q; rgb.g = hsv.v; rgb.b = p;
+ break;
+ case 2:
+ rgb.r = p; rgb.g = hsv.v; rgb.b = t;
+ break;
+ case 3:
+ rgb.r = p; rgb.g = q; rgb.b = hsv.v;
+ break;
+ case 4:
+ rgb.r = t; rgb.g = p; rgb.b = hsv.v;
+ break;
+ case 5:
+ rgb.r = hsv.v; rgb.g = p; rgb.b = q;
+ break;
+ }
+
+ return [ rgb.r * 256, rgb.g * 256, rgb.b * 256 ];
+ }
+ };
+
+ function DectoHex( dec ) {
+ var hex = parseInt( dec, 10 );
+ hex = hex.toString(16);
+ return hex == 0 ? "00" : hex;
+ }
+
+ function applySaturation( rgb ) {
+ var alpha = parseFloat(rgb[3] || 1);
+ if(alpha + "" === "NaN" || alpha < 0 || alpha >= 1) {
+ return rgb;
+ }
+ if(alpha == 0) {
+ return [ 255, 255, 255 ];
+ }
+ return [
+ alpha * parseInt(rgb[0], 10) + (1 - alpha) * (backgroundColorVal || 255),
+ alpha * parseInt(rgb[1], 10) + (1 - alpha) * (backgroundColorVal || 255),
+ alpha * parseInt(rgb[2], 10) + (1 - alpha) * (backgroundColorVal || 255)
+ ]; // assumes background is white (255)
+ }
+
+ for(var i = 0, l = otherColorTypes.length; i < l; i++) {
+ var bits = otherColorTypes[i][1].exec( color );
+ if(bits) {
+ var rgbVal = applySaturation( toRGB[ otherColorTypes[i][0] ]( bits ) );
+ return "#" + DectoHex(rgbVal[0] || 255) + DectoHex(rgbVal[1] || 255) + DectoHex(rgbVal[2] || 255);
+ }
+ }
+
+ return "#f00"; // default in case of error
+
+};
+
+function OError(name, msg, code) {
+ Error.call(this);
+ Error.captureStackTrace(this, arguments.callee);
+ this.name = name || "Error";
+ this.code = code || -1;
+ this.message = msg || "";
+};
+
+OError.prototype.__proto__ = Error.prototype;
+
+var OEvent = function(eventType, eventProperties) {
+
+ var props = eventProperties || {};
+
+ var newEvt = new CustomEvent(eventType, true, true);
+
+ for(var i in props) {
+ newEvt[i] = props[i];
+ }
+
+ return newEvt;
+
+};
+
+var OEventTarget = function() {
+
+ EventTarget.mixin( this );
+
+};
+
+OEventTarget.prototype.constructor = OEventTarget;
+
+OEventTarget.prototype.addEventListener = function(eventName, callback, useCapture) {
+ this.on(eventName, callback); // no useCapture
+};
+
+OEventTarget.prototype.removeEventListener = function(eventName, callback, useCapture) {
+ this.off(eventName, callback); // no useCapture
+}
+
+OEventTarget.prototype.dispatchEvent = function( eventObj ) {
+
+ var eventName = eventObj.type;
+
+ // Register an onX functions registered for this event, if any
+ if(typeof this[ 'on' + eventName.toLowerCase() ] === 'function') {
+ this.on( eventName, this[ 'on' + eventName.toLowerCase() ] );
+ }
+
+ this.trigger( eventName, eventObj );
+
+};
+
+var OPromise = function() {
+
+ Promise.call( this );
+
+};
+
+OPromise.prototype = Object.create( Promise.prototype );
+
+// Add OEventTarget helper functions to OPromise prototype
+for(var i in OEventTarget.prototype) {
+ OPromise.prototype[i] = OEventTarget.prototype[i];
+}
+
+/**
+ * Queue for running multi-object promise-rooted asynchronous
+ * functions serially
+ */
+var Queue = (function() {
+ var _q = [], _lock = false, _timeout = 1000;
+
+ function callNext() {
+ _lock = false;
+ dequeue(); // auto-execute next queue item
+ }
+
+ function dequeue() {
+ if (_lock) {
+ return;
+ }
+ _lock = true; // only allow one accessor at a time
+
+ var item = _q.shift(); // pop the next item from the queue
+
+ if (item === undefined) {
+ _lock = false;
+ return; // end dequeuing
+ }
+ if (item.obj.isResolved) {
+ // execute queue item immediately
+ item.fn.call(item.obj, callNext);
+ } else {
+ if(item.ignoreResolve) {
+ item.fn.call(item.obj, callNext);
+ } else {
+ // break deadlocks
+ var timer = global.setTimeout(function() {
+ console.warn('PromiseQueue deadlock broken with timeout.');
+ console.log(item.obj);
+ console.log(item.obj.isResolved);
+ item.obj.trigger('promise:resolved'); // manual trigger / resolve
+ }, _timeout);
+
+ // execute queue item when obj resolves
+ item.obj.on('promise:resolved', function() {
+ if(timer) global.clearTimeout(timer);
+
+ item.obj.isResolved = true; // set too late in rsvp.js
+
+ item.fn.call(item.obj, callNext);
+ });
+ }
+ }
+ };
+
+ return {
+ enqueue: function(obj, fn, ignoreResolve) {
+ _q.push({ "obj": obj, "fn": fn, "ignoreResolve": ignoreResolve });
+ dequeue(); // auto-execute next queue item
+ },
+ dequeue: function() {
+ dequeue();
+ }
+ }
+})();
+
+var OMessagePort = function( isBackground ) {
+
+ OEventTarget.call( this );
+
+ this._isBackground = isBackground || false;
+
+ this._localPort = null;
+
+ // Every process, except the background process needs to connect up ports
+ if( !this._isBackground ) {
+
+ this._localPort = chrome.extension.connect({ "name": ("" + Math.floor( Math.random() * 1e16)) });
+
+ this._localPort.onDisconnect.addListener(function() {
+
+ this.dispatchEvent( new OEvent( 'disconnect', { "source": this._localPort } ) );
+
+ this._localPort = null;
+
+ }.bind(this));
+
+ var onMessageHandler = function( _message, _sender, responseCallback ) {
+
+ var localPort = this._localPort;
+
+ if(_message && _message.action && _message.action.indexOf('___O_') === 0) {
+
+ // Fire controlmessage events *immediately*
+ this.dispatchEvent( new OEvent(
+ 'controlmessage',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ localPort.postMessage( data );
+ },
+ "tabId": _sender && _sender.tab ? _sender.tab.id : null
+ }
+ }
+ ) );
+
+ } else {
+
+ // Fire 'message' event once we have all the initial listeners setup on the page
+ // so we don't miss any .onconnect call from the extension page.
+ // Or immediately if the shim isReady
+ addDelayedEvent(this, 'dispatchEvent', [ new OEvent(
+ 'message',
+ {
+ "data": _message,
+ "source": {
+ postMessage: function( data ) {
+ localPort.postMessage( data );
+ },
+ "tabId": _sender && _sender.tab ? _sender.tab.id : null
+ }
+ }
+ ) ]);
+
+ }
+
+ if(responseCallback)responseCallback({});
+
+ }.bind(this);
+
+ this._localPort.onMessage.addListener( onMessageHandler );
+ chrome.extension.onMessage.addListener( onMessageHandler );
+
+
+ // Fire 'connect' event once we have all the initial listeners setup on the page
+ // so we don't miss any .onconnect call from the extension page
+ addDelayedEvent(this, 'dispatchEvent', [ new OEvent('connect', { "source": this._localPort, "origin": "" }) ]);
+
+ }
+
+};
+
+OMessagePort.prototype = Object.create( OEventTarget.prototype );
+
+OMessagePort.prototype.postMessage = function( data ) {
+
+ if( !this._isBackground ) {
+ if(this._localPort) {
+
+ this._localPort.postMessage( data );
+
+ }
+ } else {
+
+ this.broadcastMessage( data );
+
+ }
+
+};
+
+var OperaExtension = function() {
+
+ OMessagePort.call( this, false );
+
+};
+
+OperaExtension.prototype = Object.create( OMessagePort.prototype );
+
+OperaExtension.prototype.__defineGetter__('bgProcess', function() {
+ return chrome.extension.getBackgroundPage();
+});
+
+// Generate API stubs
+
+var OEX = opera.extension = opera.extension || new OperaExtension();
+
+var OEC = opera.contexts = opera.contexts || {};
+
+OperaExtension.prototype.getFile = function(path) {
+ var response = null;
+
+ if(typeof path != "string")return response;
+
+ try{
+ var host = chrome.extension.getURL('');
+
+ if(path.indexOf('widget:')==0)path = path.replace('widget:','chrome-extension:');
+ if(path.indexOf('/')==0)path = path.substring(1);
+
+ path = (path.indexOf(host)==-1?host:'')+path;
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onloadend = function(){
+ if (xhr.readyState==xhr.DONE && xhr.status==200){
+ result = xhr.response;
+
+ result.name = path.substring(path.lastIndexOf('/')+1);
+
+ result.lastModifiedDate = null;
+ result.toString = function(){
+ return "[object File]";
+ };
+ response = result;
+ };
+ };
+
+ xhr.open('GET',path,false);
+ xhr.responseType = 'blob';
+
+ xhr.send(null);
+
+ } catch(e){
+ return response;
+ };
+
+ return response;
+};
+
+// Add Widget API via the bgProcess
+global.widget = global.widget || OEX.bgProcess.widget;
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.windows = OEX.windows || OEX.bgProcess.opera.extension.windows;
+
+}
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.tabs = OEX.tabs || OEX.bgProcess.opera.extension.tabs;
+
+}
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('tabs') != -1) {
+
+ OEX.tabGroups = OEX.tabGroups || OEX.bgProcess.opera.extension.tabGroups;
+
+}
+
+if(manifest && manifest.browser_action !== undefined && manifest.browser_action !== null ) {
+
+ OEC.toolbar = OEC.toolbar || OEX.bgProcess.opera.contexts.toolbar;
+
+}
+
+if(manifest && manifest.permissions && manifest.permissions.indexOf('contextMenus')!=-1){
+
+ global.MenuItem = OEX.bgProcess.MenuItem;
+ global.MenuContext = OEX.bgProcess.MenuContext;
+
+ OEC.menu = OEC.menu || OEX.bgProcess.opera.contexts.menu;
+
+}
+
+if(global.opr && global.opr.speeddial && manifest && manifest.speeddial){
+
+ OEC.speeddial = OEC.speeddial || OEX.bgProcess.opera.contexts.speeddial;
+
+}
+
+if (global.opera) {
+ isReady = true;
+
+ // Make scripts also work in Opera <= version 12
+ opera.isReady = function(fn) {
+ fn.call(opera);
+
+ // Run delayed events (if any)
+ for(var i = 0, l = _delayedExecuteEvents.length; i < l; i++) {
+ var o = _delayedExecuteEvents[i];
+ o.target[o.methodName].apply(o.target, o.args);
+ }
+ _delayedExecuteEvents = [];
+ };
+
+} else {
+
+ opera.isReady = (function() {
+
+ var fns = {
+ "isready": [],
+ "readystatechange": [],
+ "domcontentloaded": [],
+ "load": []
+ };
+
+ var hasFired_DOMContentLoaded = false,
+ hasFired_Load = false;
+
+ // If we already missed DOMContentLoaded or Load events firing, record that now...
+ if(global.document.readyState === "interactive") {
+ hasFired_DOMContentLoaded = true;
+ }
+ if(global.document.readyState === "complete") {
+ hasFired_DOMContentLoaded = true;
+ hasFired_Load = true;
+ }
+
+ // ...otherwise catch DOMContentLoaded and Load events when they happen and set the same flag.
+ global.document.addEventListener("DOMContentLoaded", function handle_DomContentLoaded() {
+ hasFired_DOMContentLoaded = true;
+ global.document.removeEventListener("DOMContentLoaded", handle_DomContentLoaded, true);
+ }, true);
+ global.addEventListener("load", function handle_Load() {
+ hasFired_Load = true;
+ global.removeEventListener("load", handle_Load, true);
+ }, true);
+
+ // Catch and fire readystatechange events when they happen
+ global.document.addEventListener("readystatechange", function(event) {
+ event.stopImmediatePropagation();
+ event.stopPropagation();
+ if( global.document.readyState !== 'interactive' && global.document.readyState !== 'complete' ) {
+ fireEvent('readystatechange', global.document);
+ } else {
+ global.document.readyState = 'loading';
+ }
+ }, true);
+
+ // Take over handling of document.readyState via our own load bootstrap code below
+ var _readyState = (hasFired_DOMContentLoaded || hasFired_Load) ? global.document.readyState : "uninitialized";
+ global.document.__defineSetter__('readyState', function(val) { _readyState = val; });
+ global.document.__defineGetter__('readyState', function() { return _readyState; });
+
+ function interceptAddEventListener(target, _name) {
+
+ var _target = target.addEventListener;
+
+ // Replace addEventListener for given target
+ target.addEventListener = function(name, fn, usecapture) {
+ name = name + ""; // force event name to type string
+
+ if (name.toLowerCase() === _name.toLowerCase()) {
+ if (fn === undefined || fn === null ||
+ Object.prototype.toString.call(fn) !== "[object Function]") {
+ return;
+ }
+
+ if (isReady) {
+ fn.call(global);
+ } else {
+ fns[_name.toLowerCase()].push(fn);
+ }
+ } else {
+ // call standard addEventListener method on target
+ _target.call(target, name, fn, usecapture);
+ }
+ };
+
+ // Replace target.on[_name] with custom setter function
+ target.__defineSetter__("on" + _name.toLowerCase(), function( fn ) {
+ // call code block just created above...
+ target.addEventListener(_name.toLowerCase(), fn, false);
+ });
+
+ }
+
+ interceptAddEventListener(global, 'load');
+ interceptAddEventListener(global.document, 'domcontentloaded');
+ interceptAddEventListener(global, 'domcontentloaded'); // handled bubbled DOMContentLoaded events
+ interceptAddEventListener(global.document, 'readystatechange');
+
+ function fireEvent(name, target, props) {
+ var evtName = name.toLowerCase();
+
+ // Role a standard object as the Event since we really need
+ // to set the target + other unsettable properties on the
+ // isReady events
+
+ var evt = props || {};
+
+ evt.type = name;
+
+ if(!evt.target) evt.target = global;
+ if(!evt.currentTarget) evt.currentTarget = evt.target;
+ if(!evt.srcElement) evt.srcElement = evt.target;
+
+ if(evt.bubbles !== true) evt.bubbles = false;
+ if(evt.cancelable !== true) evt.cancelable = false;
+
+ if(!evt.timeStamp) evt.timeStamp = 0;
+
+ for (var i = 0, len = fns[evtName].length; i < len; i++) {
+ fns[evtName][i].call(target, evt);
+ }
+ }
+
+ function ready() {
+ global.setTimeout(function() {
+
+ if (isReady) {
+ return;
+ }
+
+ // Handle queued opera 'isReady' event functions
+ for (var i = 0, len = fns['isready'].length; i < len; i++) {
+ fns['isready'][i].call(global);
+ }
+ fns['isready'] = []; // clear
+
+ var domContentLoadedTimeoutOverride = new Date().getTime() + 120000;
+
+ // Synthesize and fire the document domcontentloaded event
+ (function fireDOMContentLoaded() {
+
+ var currentTime = new Date().getTime();
+
+ // Check for hadFired_Load in case we missed DOMContentLoaded
+ // event, in which case, we syntesize DOMContentLoaded here
+ // (always synthesized in Chromium Content Scripts)
+ if (hasFired_DOMContentLoaded || hasFired_Load || currentTime >= domContentLoadedTimeoutOverride) {
+
+ global.document.readyState = 'interactive';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('domcontentloaded', global.document, { bubbles: true }); // indicate that event bubbles
+
+ if(currentTime >= domContentLoadedTimeoutOverride) {
+ console.warn('document.domcontentloaded event fired on check timeout');
+ }
+
+ var loadTimeoutOverride = new Date().getTime() + 120000;
+
+ // Synthesize and fire the window load event
+ // after the domcontentloaded event has been
+ // fired
+ (function fireLoad() {
+
+ var currentTime = new Date().getTime();
+
+ if (hasFired_Load || currentTime >= loadTimeoutOverride) {
+
+ global.document.readyState = 'complete';
+ fireEvent('readystatechange', global.document);
+
+ fireEvent('load', global);
+
+ if(currentTime >= loadTimeoutOverride) {
+ console.warn('window.load event fired on check timeout');
+ }
+
+ // Run delayed events (if any)
+ for(var i = 0, l = _delayedExecuteEvents.length; i < l; i++) {
+ var o = _delayedExecuteEvents[i];
+ o.target[o.methodName].apply(o.target, o.args);
+ }
+ _delayedExecuteEvents = [];
+
+ } else {
+ global.setTimeout(function() {
+ fireLoad();
+ }, 50);
+ }
+
+ })();
+
+ } else {
+ global.setTimeout(function() {
+ fireDOMContentLoaded();
+ }, 50);
+ }
+
+ })();
+
+ isReady = true;
+
+ }, 0);
+ }
+
+ var holdTimeoutOverride = new Date().getTime() + 240000;
+
+ (function holdReady() {
+
+ var currentTime = new Date().getTime();
+
+ if (currentTime >= holdTimeoutOverride) {
+ // All scripts now ready to be executed: TIMEOUT override
+ console.warn('opera.isReady check timed out');
+ hasFired_Load = true; // override
+ ready();
+ return;
+ }
+
+ for (var i in deferredComponentsLoadStatus) {
+ if (deferredComponentsLoadStatus[i] !== true) {
+ // spin the loop until everything is working
+ // or we receive a timeout override (handled
+ // in next loop, above)
+ global.setTimeout(function() {
+ holdReady();
+ }, 20);
+ return;
+ }
+ }
+
+ // All scripts now ready to be executed
+ ready();
+
+ })();
+
+ return function(fn) {
+ // if the Library is already ready,
+ // execute the function immediately.
+ // otherwise, queue it up until isReady
+ if (isReady) {
+ fn.call(global);
+ } else {
+ fns['isready'].push(fn);
+ }
+ }
+ })();
+
+}
+
+// Set the width and height of the popup window to values provided in the URL query string
+opera.isReady(function() {
+
+ function getParam( key ) {
+ key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
+ var regexS = "[\\?&]" + key + "=([^]*)";
+ var regex = new RegExp(regexS);
+ var results = regex.exec(window.location.search);
+ return results == null ? "" : window.decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ window.addEventListener('DOMContentLoaded', function() {
+ var w = getParam('w'), h = getParam('h');
+ if(w !== "") {
+ document.body.style.minWidth = w.replace(/\D/g,'') + "px";
+ } else {
+ document.body.style.minWidth = "300px"; // default width
+ }
+ if(h !== "") {
+ document.body.style.minHeight = h.replace(/\D/g,'') + "px";
+ } else {
+ document.body.style.minHeight = "300px"; // default height
+ }
+ }, false);
+
+});
+
+opera.isReady(function() {
+
+ // Rewrite in-line event handlers (eg. for a sub-set of common standard events)
+
+ document.addEventListener('DOMContentLoaded', function(e) {
+
+ var selectors = ['load', 'beforeunload', 'unload', 'click', 'dblclick', 'mouseover', 'mousemove',
+ 'mousedown', 'mouseup', 'mouseout', 'keydown', 'keypress', 'keyup', 'blur', 'focus'];
+
+ for(var i = 0, l = selectors.length; i < l; i++) {
+ var els = document.querySelectorAll('[on' + selectors[i] + ']');
+ for(var j = 0, k = els.length; j < k; j++) {
+ var fn = new Function('e', els[j].getAttribute('on' + selectors[i]));
+ var target = els[j];
+ if(selectors[i].indexOf('load') > -1 && els[j] === document.body) {
+ target = window;
+ }
+
+ els[j].removeAttribute('on' + selectors[i]);
+ target.addEventListener(selectors[i], fn, true);
+ }
+ }
+
+ }, false);
+
+});
+
+ // Make API available on the window DOM object
+ global.opera = opera;
+
+})( window );
\ No newline at end of file
diff --git a/oex_shim/popup_resourceloader.html b/oex_shim/popup_resourceloader.html
new file mode 100644
index 0000000..418240a
--- /dev/null
+++ b/oex_shim/popup_resourceloader.html
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/oex_shim/popup_resourceloader.js b/oex_shim/popup_resourceloader.js
new file mode 100644
index 0000000..a632378
--- /dev/null
+++ b/oex_shim/popup_resourceloader.js
@@ -0,0 +1,13 @@
+function getParam( key ) {
+ key = key.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+ var regexS = "[\?&]" + key + "=([^]*)";
+ var regex = new RegExp(regexS);
+ var results = regex.exec(window.location.search);
+ return results == null ? "" :
+ window.decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ var s = getParam('href'), w = getParam('w'), h = getParam('h');
+ if(s !== "") { document.querySelector('iframe').src = window.atob(s); }
+ if(w !== "") { document.body.style.minWidth = w.replace(/\D/g,'') + "px"; }
+ if(h !== "") { document.body.style.minHeight = h.replace(/\D/g,'') + "px"; }
diff --git a/pop.html b/pop.html
index 1fe994f..5f0973f 100644
--- a/pop.html
+++ b/pop.html
@@ -1,23 +1,2 @@
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file